Accessibility pass: ARIA roles, label associations, CSS class migrations

- Add role=dialog/aria-modal/aria-labelledby to all 12 modal overlays (JS + PHP)
- Add aria-label="Close" to all 14 modal close buttons
- Add full ARIA combobox pattern to @mention autocomplete (listbox, option, aria-selected, aria-expanded)
- Add for= attributes to admin filter form labels (AuditLog, UserActivity, ApiKeys)
- Remove dead closeOnAdvancedSearchBackdropClick() from advanced-search.js

CSS/JS style cleanup:
- Move .ascii-banner static styles from JS inline to CSS class; add .ascii-banner--glow
- Add .ascii-banner-cursor, .loading-overlay--hiding, .has-overlay, tr[data-clickable]
- Add .animate-fadein/.animate-fadeout/.comment--deleting to ticket.css
- Add .lt-toast--hiding to base.css; remove opacity/transition inline JS
- Remove redundant cursor:pointer JS (already in th{} CSS rule)
- Remove trailing space in lt-select class attributes

Bug fixes:
- base.js: boot overlay opacity inline style was overriding .fade-out class opacity via
  specificity (1000 vs 20), preventing the fade-out animation — removed
- ascii-banner.js: cursor used blink-caret (border-color only) instead of blink-cursor
  (opacity-based), so the █ cursor never actually blinked — fixed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 20:29:58 -04:00
parent 11f75fd823
commit 7695c6134c
21 changed files with 929 additions and 610 deletions

View File

@@ -51,14 +51,13 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<title>Ticket #<?php echo $ticket['ticket_id']; ?></title>
<link rel="icon" type="image/png" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/images/favicon.png">
<link rel="stylesheet" href="/assets/css/base.css">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260319d">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css?v=20260319d">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/dashboard.css?v=20260320">
<link rel="stylesheet" href="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/css/ticket.css?v=20260320">
<script nonce="<?php echo $nonce; ?>" src="/assets/js/base.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/toast.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/utils.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/markdown.js?v=20260131e"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260205"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/ticket.js?v=20260131e"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/utils.js?v=20260320"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/markdown.js?v=20260320"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/dashboard.js?v=20260320"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/ticket.js?v=20260320"></script>
<script nonce="<?php echo $nonce; ?>">
// CSRF Token for AJAX requests
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
@@ -428,7 +427,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<p>Drag and drop files here or click to browse</p>
<p class="upload-hint">Max file size: <?php echo $GLOBALS['config']['MAX_UPLOAD_SIZE'] ? number_format($GLOBALS['config']['MAX_UPLOAD_SIZE'] / 1048576, 0) . 'MB' : '10MB'; ?></p>
<input type="file" id="fileInput" multiple class="sr-only" aria-label="Upload files">
<button type="button" id="browseFilesBtn" class="btn upload-browse-btn">Browse Files</button>
<button type="button" id="browseFilesBtn" class="btn upload-browse-btn">BROWSE FILES</button>
</div>
</div>
<div id="uploadProgress" class="upload-progress" style="display: none;">
@@ -561,39 +560,36 @@ $nonce = SecurityHeadersMiddleware::getNonce();
var cloneBtn = document.getElementById('cloneButton');
if (cloneBtn) {
cloneBtn.addEventListener('click', function() {
if (confirm('Create a copy of this ticket? The new ticket will have the same title, description, priority, category, and type.')) {
cloneBtn.disabled = true;
cloneBtn.textContent = 'Cloning...';
showConfirmModal(
'Clone Ticket',
'Create a copy of this ticket? The new ticket will have the same title, description, priority, category, and type.',
'warning',
function() {
cloneBtn.disabled = true;
cloneBtn.textContent = 'Cloning...';
fetch('/api/clone_ticket.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
lt.api.post('/api/clone_ticket.php', {
ticket_id: window.ticketData.ticket_id
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
toast.success('Ticket cloned successfully!');
setTimeout(function() {
window.location.href = '/ticket/' + data.new_ticket_id;
}, 1000);
} else {
toast.error('Failed to clone ticket: ' + (data.error || 'Unknown error'));
.then(data => {
if (data.success) {
lt.toast.success('Ticket cloned successfully!');
setTimeout(function() {
window.location.href = '/ticket/' + data.new_ticket_id;
}, 1000);
} else {
lt.toast.error('Failed to clone ticket: ' + (data.error || 'Unknown error'));
cloneBtn.disabled = false;
cloneBtn.textContent = 'Clone';
}
})
.catch(function(error) {
lt.toast.error('Failed to clone ticket: ' + error.message);
cloneBtn.disabled = false;
cloneBtn.textContent = 'Clone';
}
})
.catch(function(error) {
toast.error('Failed to clone ticket: ' + error.message);
cloneBtn.disabled = false;
cloneBtn.textContent = 'Clone';
});
}
});
}
);
});
}
@@ -817,8 +813,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
</div>
</div>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/keyboard-shortcuts.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/settings.js"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/keyboard-shortcuts.js?v=20260320"></script>
<script nonce="<?php echo $nonce; ?>" src="<?php echo $GLOBALS['config']['ASSETS_URL']; ?>/js/settings.js?v=20260320"></script>
<script nonce="<?php echo $nonce; ?>">if (window.lt) lt.keys.initDefaults();</script>
</body>
</html>