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:
@@ -1792,6 +1792,7 @@ select option:checked {
|
||||
transition: transform 0.2s ease, opacity 0.2s ease;
|
||||
box-shadow: var(--glow-cyan);
|
||||
}
|
||||
.lt-menu-btn:active { opacity: 0.7; }
|
||||
.lt-menu-btn:focus-visible { outline: 1px solid var(--accent-cyan); outline-offset: 2px; }
|
||||
.lt-menu-btn.open span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
|
||||
.lt-menu-btn.open span:nth-child(2) { opacity: 0; }
|
||||
@@ -2472,7 +2473,9 @@ select option:checked {
|
||||
transform: translateX(-50%) translateY(4px);
|
||||
}
|
||||
[data-tooltip]:hover::before,
|
||||
[data-tooltip]:hover::after {
|
||||
[data-tooltip]:focus-visible::before,
|
||||
[data-tooltip]:hover::after,
|
||||
[data-tooltip]:focus-visible::after {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
@@ -2490,7 +2493,9 @@ select option:checked {
|
||||
transform: translateX(-50%) translateY(-4px);
|
||||
}
|
||||
[data-tooltip][data-tooltip-pos="bottom"]:hover::before,
|
||||
[data-tooltip][data-tooltip-pos="bottom"]:hover::after {
|
||||
[data-tooltip][data-tooltip-pos="bottom"]:focus-visible::before,
|
||||
[data-tooltip][data-tooltip-pos="bottom"]:hover::after,
|
||||
[data-tooltip][data-tooltip-pos="bottom"]:focus-visible::after {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
@@ -2551,6 +2556,12 @@ select option:checked {
|
||||
box-shadow: var(--glow-orange);
|
||||
font-weight: 700;
|
||||
}
|
||||
.lt-page-btn:focus-visible {
|
||||
outline: 2px solid var(--accent-cyan);
|
||||
outline-offset: 1px;
|
||||
border-color: var(--accent-cyan);
|
||||
color: var(--accent-cyan);
|
||||
}
|
||||
.lt-page-btn:disabled,
|
||||
.lt-page-btn[aria-disabled="true"] {
|
||||
opacity: 0.35;
|
||||
@@ -2584,6 +2595,7 @@ select option:checked {
|
||||
text-align: left;
|
||||
}
|
||||
.lt-accordion-header:hover { background: var(--bg-tertiary); color: var(--accent-cyan); }
|
||||
.lt-accordion-header:active { background: var(--bg-tertiary); opacity: 0.8; }
|
||||
.lt-accordion-header:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; }
|
||||
.lt-accordion-header[aria-expanded="true"] { color: var(--accent-orange); }
|
||||
.lt-accordion-icon {
|
||||
@@ -2641,6 +2653,7 @@ select option:checked {
|
||||
transition: color 0.15s;
|
||||
}
|
||||
.lt-alert-close:hover { color: var(--accent-red); }
|
||||
.lt-alert-close:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; }
|
||||
.lt-alert.dismissed { max-height: 0 !important; opacity: 0; padding-top: 0; padding-bottom: 0; pointer-events: none; }
|
||||
|
||||
|
||||
@@ -2655,7 +2668,21 @@ select option:checked {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.lt-toggle input { display: none; }
|
||||
/* Visually hidden but still keyboard-focusable */
|
||||
.lt-toggle input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 1px; height: 1px;
|
||||
margin: -1px; padding: 0;
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
clip-path: inset(50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.lt-toggle input:focus-visible ~ .lt-toggle-track {
|
||||
outline: 2px solid var(--accent-cyan);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.lt-toggle-track {
|
||||
width: 36px;
|
||||
height: 18px;
|
||||
@@ -2909,6 +2936,7 @@ input[type="range"].lt-range::-moz-range-thumb {
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.lt-code-copy:hover { border-color: var(--accent-cyan); color: var(--accent-cyan); }
|
||||
.lt-code-copy:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; }
|
||||
.lt-code-copy.copied { border-color: var(--accent-green); color: var(--accent-green); }
|
||||
.lt-code-block pre {
|
||||
margin: 0;
|
||||
@@ -4275,6 +4303,7 @@ html[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: var(--acc
|
||||
white-space: nowrap;
|
||||
}
|
||||
.lt-context-menu-item:hover { background: var(--accent-cyan-dim); color: var(--accent-cyan); }
|
||||
.lt-context-menu-item:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; background: var(--accent-cyan-dim); color: var(--accent-cyan); }
|
||||
.lt-context-menu-item.is-danger:hover { background: var(--accent-red-dim); color: var(--accent-red); }
|
||||
.lt-context-menu-item .icon { width: 1rem; text-align: center; opacity: 0.7; font-size: 0.75rem; }
|
||||
.lt-context-menu-item kbd {
|
||||
@@ -4900,6 +4929,9 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
.lt-lightbox-close:hover,
|
||||
.lt-lightbox-prev:hover,
|
||||
.lt-lightbox-next:hover { color: var(--accent-cyan); border-color: var(--accent-cyan-border); box-shadow: var(--box-glow-cyan); }
|
||||
.lt-lightbox-close:focus-visible,
|
||||
.lt-lightbox-prev:focus-visible,
|
||||
.lt-lightbox-next:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; color: var(--accent-cyan); }
|
||||
.lt-lightbox-caption {
|
||||
position: fixed;
|
||||
bottom: 3rem;
|
||||
@@ -4947,6 +4979,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
border-radius: 2px;
|
||||
}
|
||||
.lt-sidebar-group-label:hover { color: var(--text-secondary); }
|
||||
.lt-sidebar-group-label:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 1px; border-radius: 2px; }
|
||||
.lt-sidebar-group-label .chevron {
|
||||
font-size: 0.5rem;
|
||||
transition: transform 0.2s ease;
|
||||
@@ -5066,7 +5099,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
transition: opacity 0.15s ease, transform 0.15s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
.lt-notif-panel[aria-hidden="false"] {
|
||||
.lt-notif-panel:not([aria-hidden]) {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
pointer-events: auto;
|
||||
@@ -5111,6 +5144,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
transition: background 0.1s;
|
||||
}
|
||||
.lt-notif-item:hover { background: var(--bg-tertiary); }
|
||||
.lt-notif-item:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; background: var(--bg-tertiary); }
|
||||
.lt-notif-item--unread { background: rgba(0, 212, 255, 0.04); }
|
||||
|
||||
.lt-notif-dot {
|
||||
@@ -5179,7 +5213,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
right: 0;
|
||||
transform-origin: top right;
|
||||
}
|
||||
.lt-dropdown-panel[aria-hidden="false"] {
|
||||
.lt-dropdown-panel:not([aria-hidden]) {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
pointer-events: auto;
|
||||
@@ -5206,6 +5240,12 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.lt-dropdown-item:focus-visible {
|
||||
outline: 2px solid var(--accent-cyan);
|
||||
outline-offset: -2px;
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.lt-dropdown-item--danger { color: var(--accent-red); }
|
||||
.lt-dropdown-item--danger:hover { background: rgba(255,45,85,0.1); color: var(--accent-red); }
|
||||
|
||||
|
||||
@@ -123,34 +123,34 @@
|
||||
<!-- Notifications with badge + dropdown -->
|
||||
<div class="lt-notif-dropdown-wrap" id="lt-notif-bell">
|
||||
<button class="lt-btn lt-btn-sm lt-notif-bell-btn" id="lt-notif-bell-btn" aria-label="Open notifications" aria-expanded="false" aria-haspopup="true" style="padding:0 0.6rem;">🔔</button>
|
||||
<div class="lt-notif-panel" id="lt-notif-panel" aria-hidden="true">
|
||||
<div class="lt-notif-panel" id="lt-notif-panel" role="region" aria-label="Notifications" aria-hidden="true">
|
||||
<div class="lt-notif-panel-header">
|
||||
<span>Notifications</span>
|
||||
<button class="lt-notif-panel-clear" id="lt-notif-clear-all">Mark all read</button>
|
||||
</div>
|
||||
<div class="lt-notif-panel-list">
|
||||
<div class="lt-notif-item lt-notif-item--unread">
|
||||
<div class="lt-notif-item lt-notif-item--unread" role="button" tabindex="0">
|
||||
<span class="lt-notif-dot"></span>
|
||||
<div class="lt-notif-item-body">
|
||||
<div class="lt-notif-item-title">P1 alert: storage link-down</div>
|
||||
<div class="lt-notif-item-time">5 min ago</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lt-notif-item lt-notif-item--unread">
|
||||
<div class="lt-notif-item lt-notif-item--unread" role="button" tabindex="0">
|
||||
<span class="lt-notif-dot"></span>
|
||||
<div class="lt-notif-item-body">
|
||||
<div class="lt-notif-item-title">Worker node-03 reconnected</div>
|
||||
<div class="lt-notif-item-time">12 min ago</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lt-notif-item lt-notif-item--unread">
|
||||
<div class="lt-notif-item lt-notif-item--unread" role="button" tabindex="0">
|
||||
<span class="lt-notif-dot"></span>
|
||||
<div class="lt-notif-item-body">
|
||||
<div class="lt-notif-item-title">Export CSV complete — 42 rows</div>
|
||||
<div class="lt-notif-item-time">1 hr ago</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lt-notif-item">
|
||||
<div class="lt-notif-item" role="button" tabindex="0">
|
||||
<span class="lt-notif-dot lt-notif-dot--read"></span>
|
||||
<div class="lt-notif-item-body">
|
||||
<div class="lt-notif-item-title">Scheduled maintenance completed</div>
|
||||
@@ -359,9 +359,9 @@
|
||||
</fieldset>
|
||||
|
||||
<div class="lt-filter-group">
|
||||
<span class="lt-filter-label">Assigned To</span>
|
||||
<label class="lt-filter-label" for="filter-assigned-to">Assigned To</label>
|
||||
<div class="lt-form-group">
|
||||
<select class="lt-select lt-btn-sm">
|
||||
<select class="lt-select lt-btn-sm" id="filter-assigned-to">
|
||||
<option value="">All users</option>
|
||||
<option>operator</option>
|
||||
<option>admin</option>
|
||||
@@ -559,19 +559,19 @@
|
||||
<div class="lt-section-header">Open</div>
|
||||
<div class="lt-section-body" id="kanban-col-open" style="min-height:60px">
|
||||
|
||||
<div class="lt-card lt-mb-md lt-row-p1">
|
||||
<div class="lt-card lt-mb-md lt-row-p1" role="article" tabindex="0" aria-label="P1 — Storage array link-down, 5m ago, Unassigned">
|
||||
<div class="lt-flex lt-flex-between lt-mb-md">
|
||||
<span class="lt-p1">P1</span>
|
||||
<span class="lt-dot lt-dot-up"></span>
|
||||
<span class="lt-p1" aria-hidden="true">P1</span>
|
||||
<span class="lt-dot lt-dot-up" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="lt-text-sm">Storage array link-down</div>
|
||||
<div class="lt-text-xs lt-text-muted lt-mt-sm">5m ago · Unassigned</div>
|
||||
</div>
|
||||
|
||||
<div class="lt-card lt-row-p3">
|
||||
<div class="lt-card lt-row-p3" role="article" tabindex="0" aria-label="P3 — Update node_exporter on micro1, 1d ago, operator">
|
||||
<div class="lt-flex lt-flex-between lt-mb-md">
|
||||
<span class="lt-p3">P3</span>
|
||||
<span class="lt-dot lt-dot-up"></span>
|
||||
<span class="lt-p3" aria-hidden="true">P3</span>
|
||||
<span class="lt-dot lt-dot-up" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="lt-text-sm">Update node_exporter on micro1</div>
|
||||
<div class="lt-text-xs lt-text-muted lt-mt-sm">1d ago · operator</div>
|
||||
@@ -586,10 +586,10 @@
|
||||
<span class="lt-frame-br">╝</span>
|
||||
<div class="lt-section-header">Pending</div>
|
||||
<div class="lt-section-body" id="kanban-col-pending" style="min-height:60px">
|
||||
<div class="lt-card lt-row-p2">
|
||||
<div class="lt-card lt-row-p2" role="article" tabindex="0" aria-label="P2 — Scheduled maintenance window, 2d ago, admin">
|
||||
<div class="lt-flex lt-flex-between lt-mb-md">
|
||||
<span class="lt-p2">P2</span>
|
||||
<span class="lt-dot lt-dot-warn"></span>
|
||||
<span class="lt-p2" aria-hidden="true">P2</span>
|
||||
<span class="lt-dot lt-dot-warn" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="lt-text-sm">Scheduled maintenance window</div>
|
||||
<div class="lt-text-xs lt-text-muted lt-mt-sm">2d ago · admin</div>
|
||||
@@ -603,10 +603,10 @@
|
||||
<span class="lt-frame-br">╝</span>
|
||||
<div class="lt-section-header">In Progress</div>
|
||||
<div class="lt-section-body" id="kanban-col-inprogress" style="min-height:60px">
|
||||
<div class="lt-card lt-row-p2 lt-item-running">
|
||||
<div class="lt-card lt-row-p2 lt-item-running" role="article" tabindex="0" aria-label="P2 — Switch port flapping on USW-Pro, 2h ago, operator">
|
||||
<div class="lt-flex lt-flex-between lt-mb-md">
|
||||
<span class="lt-p2">P2</span>
|
||||
<span class="lt-dot lt-dot-warn"></span>
|
||||
<span class="lt-p2" aria-hidden="true">P2</span>
|
||||
<span class="lt-dot lt-dot-warn" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="lt-text-sm">Switch port flapping on USW-Pro</div>
|
||||
<div class="lt-text-xs lt-text-muted lt-mt-sm">2h ago · operator</div>
|
||||
@@ -620,10 +620,10 @@
|
||||
<span class="lt-frame-br">╝</span>
|
||||
<div class="lt-section-header">Closed</div>
|
||||
<div class="lt-section-body" id="kanban-col-closed" style="min-height:60px">
|
||||
<div class="lt-card lt-row-p4" style="opacity:0.6">
|
||||
<div class="lt-card lt-row-p4" style="opacity:0.6" role="article" tabindex="0" aria-label="P4 — Update SSL cert on wiki, 3d ago, operator">
|
||||
<div class="lt-flex lt-flex-between lt-mb-md">
|
||||
<span class="lt-p4">P4</span>
|
||||
<span class="lt-dot lt-dot-idle"></span>
|
||||
<span class="lt-p4" aria-hidden="true">P4</span>
|
||||
<span class="lt-dot lt-dot-idle" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="lt-text-sm">Update SSL cert on wiki</div>
|
||||
<div class="lt-text-xs lt-text-muted lt-mt-sm">3d ago · operator</div>
|
||||
@@ -1418,7 +1418,7 @@
|
||||
<!-- Counter -->
|
||||
<p class="lt-wizard-counter">Step <strong data-wizard-current>1</strong> of <strong data-wizard-total>3</strong></p>
|
||||
<!-- Steps -->
|
||||
<div data-wizard-step="1" class="is-active" aria-hidden="false">
|
||||
<div data-wizard-step="1" class="is-active">
|
||||
<div class="lt-grid lt-grid-2" style="gap:1rem;margin-top:1rem">
|
||||
<div class="lt-form-group"><label class="lt-label">Ticket Title</label><input type="text" name="title" class="lt-input lt-w-full" placeholder="Brief description…"></div>
|
||||
<div class="lt-form-group"><label class="lt-label">Priority</label><select name="priority" class="lt-select lt-w-full"><option value="p1">P1 Critical</option><option value="p2">P2 High</option><option value="p3" selected>P3 Medium</option><option value="p4">P4 Low</option></select></div>
|
||||
@@ -1876,7 +1876,7 @@ Storage array link-down on `compute-storage-01`.
|
||||
if (!btn || !panel) return;
|
||||
|
||||
function open() {
|
||||
panel.setAttribute('aria-hidden', 'false');
|
||||
panel.removeAttribute('aria-hidden');
|
||||
btn.setAttribute('aria-expanded', 'true');
|
||||
// Mark all as read visually
|
||||
}
|
||||
@@ -1887,7 +1887,7 @@ Storage array link-down on `compute-storage-01`.
|
||||
|
||||
btn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
panel.getAttribute('aria-hidden') === 'false' ? close() : open();
|
||||
panel.hasAttribute('aria-hidden') ? open() : close();
|
||||
});
|
||||
|
||||
// "Mark all read" button
|
||||
@@ -1899,14 +1899,16 @@ Storage array link-down on `compute-storage-01`.
|
||||
lt.toast.info('All notifications marked as read');
|
||||
});
|
||||
|
||||
// Individual item click
|
||||
// Individual item click + keyboard activation
|
||||
panel.querySelectorAll('.lt-notif-item').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
const activate = () => {
|
||||
item.classList.remove('lt-notif-item--unread');
|
||||
const dot = item.querySelector('.lt-notif-dot');
|
||||
if (dot) { dot.classList.add('lt-notif-dot--read'); }
|
||||
close();
|
||||
});
|
||||
};
|
||||
item.addEventListener('click', activate);
|
||||
item.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); activate(); } });
|
||||
});
|
||||
|
||||
// Close on outside click
|
||||
@@ -1926,15 +1928,15 @@ Storage array link-down on `compute-storage-01`.
|
||||
|
||||
btn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
const isOpen = panel.getAttribute('aria-hidden') === 'false';
|
||||
const isOpen = !panel.hasAttribute('aria-hidden');
|
||||
// Close all other dropdowns first
|
||||
document.querySelectorAll('.lt-dropdown-panel[aria-hidden="false"]').forEach(p => {
|
||||
document.querySelectorAll('.lt-dropdown-panel:not([aria-hidden])').forEach(p => {
|
||||
p.setAttribute('aria-hidden', 'true');
|
||||
const t = p.closest('.lt-dropdown-wrap').querySelector('.lt-dropdown-trigger');
|
||||
if (t) t.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
if (!isOpen) {
|
||||
panel.setAttribute('aria-hidden', 'false');
|
||||
panel.removeAttribute('aria-hidden');
|
||||
btn.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
});
|
||||
@@ -1942,7 +1944,7 @@ Storage array link-down on `compute-storage-01`.
|
||||
|
||||
// Close dropdowns on outside click / Esc
|
||||
document.addEventListener('click', () => {
|
||||
document.querySelectorAll('.lt-dropdown-panel[aria-hidden="false"]').forEach(p => {
|
||||
document.querySelectorAll('.lt-dropdown-panel:not([aria-hidden])').forEach(p => {
|
||||
p.setAttribute('aria-hidden', 'true');
|
||||
const t = p.closest('.lt-dropdown-wrap').querySelector('.lt-dropdown-trigger');
|
||||
if (t) t.setAttribute('aria-expanded', 'false');
|
||||
|
||||
@@ -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 => {
|
||||
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