mirror of
https://github.com/microsoft/autogen.git
synced 2025-12-04 10:59:52 +00:00
[Accessibility] Fix: screen reader does not announce theme change and nested nav label (#6061)
## Why are these changes needed? fix the accessibility issue that screen reader doesn't announce the theme when it changes ## Related issue number #5631 (13) (31) (59) --------- Co-authored-by: peterychang <49209570+peterychang@users.noreply.github.com>
This commit is contained in:
parent
26364e3dfb
commit
6f784ac186
@ -1,4 +1,6 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
let liveRegion = createLiveRegion();
|
||||
|
||||
document.querySelectorAll('.copybtn').forEach(button => {
|
||||
// Return focus to copy button after activation
|
||||
button.addEventListener('click', async function (event) {
|
||||
@ -7,6 +9,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
// Perform the copy action
|
||||
await copyToClipboard(this);
|
||||
announceMessage(liveRegion, 'Copied to clipboard');
|
||||
|
||||
// Restore the focus
|
||||
focusedElement.focus();
|
||||
@ -29,7 +32,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
|
||||
// Set active TOCtree elements with aria-current=page
|
||||
document.querySelectorAll('.bd-sidenav .active').forEach(function(element) {
|
||||
document.querySelectorAll('.bd-sidenav .active').forEach(function (element) {
|
||||
element.setAttribute('aria-current', 'page');
|
||||
});
|
||||
|
||||
@ -47,9 +50,27 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
});
|
||||
|
||||
const themeButton = document.querySelector('.theme-switch-button');
|
||||
if (themeButton) {
|
||||
themeButton.addEventListener('click', function () {
|
||||
const mode = document.documentElement.getAttribute('data-mode');
|
||||
announceMessage(liveRegion, `Theme changed to ${mode}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Enhance TOC sections for accessibility
|
||||
document.querySelectorAll('.caption-text').forEach(caption => {
|
||||
const sectionTitle = caption.textContent.trim();
|
||||
const captionContainer = caption.closest('p.caption');
|
||||
if (!captionContainer) return;
|
||||
|
||||
// Find and process navigation lists that belong to this section
|
||||
findSectionNav(captionContainer, sectionTitle);
|
||||
});
|
||||
|
||||
// Version dropdown menu is dynamically generated after page load. Listen for changes to set aria-selected
|
||||
var observer = new MutationObserver(function() {
|
||||
document.querySelectorAll('.dropdown-item').forEach(function(element) {
|
||||
var observer = new MutationObserver(function () {
|
||||
document.querySelectorAll('.dropdown-item').forEach(function (element) {
|
||||
if (element.classList.contains('active')) {
|
||||
element.setAttribute('aria-selected', 'true');
|
||||
}
|
||||
@ -61,7 +82,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
var config = { childList: true, subtree: true };
|
||||
|
||||
if (targetNode) {
|
||||
observer.observe(targetNode, config);
|
||||
observer.observe(targetNode, config);
|
||||
}
|
||||
});
|
||||
|
||||
@ -70,29 +91,118 @@ async function copyToClipboard(button) {
|
||||
const codeBlock = document.querySelector(targetSelector);
|
||||
try {
|
||||
await navigator.clipboard.writeText(codeBlock.textContent);
|
||||
|
||||
// Add a visually hidden element for screen readers to announce
|
||||
const srAnnouncement = document.createElement('div');
|
||||
srAnnouncement.textContent = 'Copied to clipboard';
|
||||
srAnnouncement.setAttribute('role', 'status');
|
||||
srAnnouncement.setAttribute('aria-live', 'polite');
|
||||
srAnnouncement.style.position = 'absolute';
|
||||
srAnnouncement.style.width = '1px';
|
||||
srAnnouncement.style.height = '1px';
|
||||
srAnnouncement.style.padding = '0';
|
||||
srAnnouncement.style.margin = '-1px';
|
||||
srAnnouncement.style.overflow = 'hidden';
|
||||
srAnnouncement.style.clipPath = 'inset(50%)';
|
||||
srAnnouncement.style.whiteSpace = 'nowrap';
|
||||
srAnnouncement.style.border = '0';
|
||||
|
||||
document.body.appendChild(srAnnouncement);
|
||||
|
||||
// Remove the announcement element after it's been read
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(srAnnouncement);
|
||||
}, 3000);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy text: ', err);
|
||||
}
|
||||
}
|
||||
|
||||
function createLiveRegion() {
|
||||
const liveRegion = document.createElement('div');
|
||||
liveRegion.setAttribute('role', 'status');
|
||||
liveRegion.setAttribute('aria-live', 'assertive');
|
||||
liveRegion.style.position = 'absolute';
|
||||
liveRegion.style.width = '1px';
|
||||
liveRegion.style.height = '1px';
|
||||
liveRegion.style.padding = '0';
|
||||
liveRegion.style.margin = '-1px';
|
||||
liveRegion.style.overflow = 'hidden';
|
||||
liveRegion.style.clipPath = 'inset(50%)';
|
||||
liveRegion.style.whiteSpace = 'nowrap'; ` `
|
||||
liveRegion.style.border = '0';
|
||||
document.body.appendChild(liveRegion);
|
||||
|
||||
return liveRegion;
|
||||
}
|
||||
|
||||
function announceMessage(liveRegion, message) {
|
||||
liveRegion.textContent = '';
|
||||
setTimeout(() => {
|
||||
liveRegion.textContent = message;
|
||||
}, 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find navigation lists belonging to a section and process them
|
||||
*/
|
||||
function findSectionNav(captionContainer, sectionTitle) {
|
||||
let nextElement = captionContainer.nextElementSibling;
|
||||
|
||||
while (nextElement) {
|
||||
if (nextElement.classList && nextElement.classList.contains('caption')) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (nextElement.matches('ul.bd-sidenav')) {
|
||||
enhanceNavList(nextElement, sectionTitle);
|
||||
}
|
||||
|
||||
nextElement = nextElement.nextElementSibling;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a navigation list by enhancing its links for accessibility
|
||||
*/
|
||||
function enhanceNavList(navList, sectionTitle) {
|
||||
const topLevelItems = navList.querySelectorAll(':scope > li');
|
||||
|
||||
topLevelItems.forEach(item => {
|
||||
const link = item.querySelector(':scope > a.reference.internal');
|
||||
if (!link) return;
|
||||
|
||||
const linkText = link.textContent.trim();
|
||||
link.setAttribute('aria-label', `${sectionTitle}: ${linkText}`);
|
||||
|
||||
enhanceExpandableSections(item, link, linkText, sectionTitle);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Process expandable sections (details elements) within a navigation item
|
||||
*/
|
||||
function enhanceExpandableSections(item, parentLink, parentText, sectionTitle) {
|
||||
const detailsElements = item.querySelectorAll('details');
|
||||
|
||||
detailsElements.forEach(details => {
|
||||
enhanceToggleButton(details, parentText);
|
||||
enhanceNestedLinks(details, parentLink, parentText, sectionTitle);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Make toggle buttons more accessible by adding appropriate aria labels
|
||||
*/
|
||||
function enhanceToggleButton(details, parentText) {
|
||||
const summary = details.querySelector('summary');
|
||||
if (!summary) return;
|
||||
|
||||
function updateToggleLabel() {
|
||||
const isExpanded = details.hasAttribute('open');
|
||||
const action = isExpanded ? 'Collapse' : 'Expand';
|
||||
summary.setAttribute('aria-label', `${action} ${parentText} section`);
|
||||
}
|
||||
|
||||
updateToggleLabel();
|
||||
|
||||
summary.addEventListener('click', () => {
|
||||
setTimeout(updateToggleLabel, 10);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance nested links with hierarchical aria-labels
|
||||
*/
|
||||
function enhanceNestedLinks(details, parentLink, parentText, sectionTitle) {
|
||||
const nestedLinks = details.querySelectorAll('a.reference.internal');
|
||||
|
||||
nestedLinks.forEach(link => {
|
||||
const linkText = link.textContent.trim();
|
||||
const parentLabel = parentLink.getAttribute('aria-label');
|
||||
|
||||
if (parentLabel) {
|
||||
link.setAttribute('aria-label', `${parentLabel}: ${linkText}`);
|
||||
} else {
|
||||
link.setAttribute('aria-label', `${sectionTitle}: ${parentText}: ${linkText}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user