Skip to content

Commit ea91fcd

Browse files
authored
misc: apply docs accessibility violation fixes (#1112)
1 parent f22e4bd commit ea91fcd

File tree

3 files changed

+175
-1
lines changed

3 files changed

+175
-1
lines changed

build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ allprojects {
5454
],
5555
"customAssets": [
5656
"${rootProject.file("docs/dokka-presets/assets/logo-icon.svg")}",
57-
"${rootProject.file("docs/dokka-presets/assets/aws_logo_white_59x35.png")}"
57+
"${rootProject.file("docs/dokka-presets/assets/aws_logo_white_59x35.png")}",
58+
"${rootProject.file("docs/dokka-presets/scripts/accessibility.js")}"
5859
],
5960
"footerMessage": "© $year, Amazon Web Services, Inc. or its affiliates. All rights reserved.",
6061
"separateInheritedMembers" : true,

docs/dokka-presets/css/aws-styles.css

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,32 @@
6363
div .compiler-info, .fold-button, .run-button {
6464
display: none;
6565
}
66+
67+
.skip-to-content {
68+
width: 1px;
69+
height: 1px;
70+
overflow: hidden;
71+
opacity: 0;
72+
}
73+
74+
.skip-to-content:focus,
75+
.skip-to-content:active {
76+
width: auto;
77+
height: auto;
78+
opacity: 1;
79+
z-index: 999; /* Ensure the skip link is on top of other content */
80+
}
81+
82+
.aws-toggle-content-btn {
83+
font-size: 24px;
84+
background: none;
85+
border: none;
86+
cursor: pointer;
87+
padding: 8px;
88+
}
89+
90+
@media (max-width: 550px) {
91+
.content[data-togglable] {
92+
display: none;
93+
}
94+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/**
2+
* Apply "skip to main content" buttons after each active left sidebar `sideMenuPart`.
3+
* These are invisible and only accessible via keyboard
4+
* Fixes accessibility violation: "Provide a mechanism for skipping past repetitive content"
5+
*/
6+
function applySkipLinks() {
7+
function insertSkipLink(element) {
8+
if (element.querySelectorAll(".skip-to-content").length > 0) { return }
9+
10+
const skipLink = document.createElement('div');
11+
// Create an anchor element with the href pointing to the main content
12+
const anchor = document.createElement('a');
13+
anchor.classList.add('skip-to-content');
14+
anchor.href = '#content';
15+
anchor.innerHTML = 'Skip to Main Content';
16+
anchor.setAttribute("tabindex", "0");
17+
skipLink.appendChild(anchor);
18+
if (element.children.length > 1) {
19+
element.insertBefore(skipLink, element.children[1]);
20+
} else {
21+
element.appendChild(skipLink);
22+
}
23+
}
24+
25+
function handleChanges(mutationsList) {
26+
for (const mutation of mutationsList) {
27+
if (mutation.type === 'attributes' && mutation.target.classList.contains('sideMenuPart') && !mutation.target.classList.contains('hidden')) {
28+
insertSkipLink(mutation.target);
29+
}
30+
}
31+
32+
// Insert a skip link on all sideMenuParts with [data-active] property
33+
document.querySelectorAll('.sideMenuPart[data-active]').forEach(function(sideMenuPart) {
34+
insertSkipLink(sideMenuPart)
35+
});
36+
}
37+
38+
const observer = new MutationObserver(handleChanges);
39+
const observerConfig = {
40+
childList: true,
41+
subtree: true,
42+
attributes: true,
43+
attributeFilter: ['class']
44+
};
45+
observer.observe(document.body, observerConfig);
46+
}
47+
document.addEventListener('DOMContentLoaded', applySkipLinks);
48+
if (document.readyState === "interactive" || document.readyState === "complete" ) { applySkipLinks() }
49+
50+
51+
/**
52+
* Ensure `navButton` elements are interactable and have proper accessibility properties
53+
* Fixes accessibilty violation: "Ensure all interactive functionality is operable with the keyboard"
54+
*/
55+
function ensureNavButtonInteractable() {
56+
const navButtons = document.querySelectorAll('.navButton');
57+
58+
navButtons.forEach(function(navButton) {
59+
// Make the navButton focusable, add accessibility information
60+
navButton.setAttribute('tabindex', '0');
61+
navButton.setAttribute('role', 'button');
62+
navButton.setAttribute('aria-expanded', 'false');
63+
64+
// Grab the page ID, use it for aria-label and aria-controls
65+
const sectionName = navButton.parentElement.parentElement.getAttribute('pageid')
66+
// Remove the page ID suffix auto-generated by Dokka
67+
const cleanedSectionName = sectionName.substring(0, sectionName.indexOf("////PointingToDeclaration"))
68+
navButton.setAttribute('aria-label', cleanedSectionName);
69+
70+
const sectionID = navButton.parentElement.parentElement.id
71+
navButton.setAttribute('aria-controls', sectionID);
72+
73+
// Add event listener for Enter and Space keys
74+
navButton.addEventListener('keydown', function(event) {
75+
if (event.key === 'Enter' || event.key === ' ') {
76+
event.preventDefault(); // Prevent the default action to avoid navigation
77+
this.click(); // Trigger the onclick event
78+
}
79+
});
80+
81+
// Update aria-expanded attribute on click
82+
navButton.addEventListener('click', function() {
83+
const isExpanded = navButton.getAttribute('aria-expanded') === 'true';
84+
navButton.setAttribute('aria-expanded', (!isExpanded).toString());
85+
});
86+
});
87+
}
88+
89+
window.onload = function() {
90+
ensureNavButtonInteractable()
91+
}
92+
93+
//
94+
/**
95+
* Ensure that content (specifically, code blocks) reflows on small page sizes.
96+
* Fixes accessibility violation: "Ensure pages reflow without requiring two-dimensional scrolling without loss of content or functionality"
97+
*/
98+
function ensureContentReflow() {
99+
const MIN_WINDOW_SIZE = 550
100+
101+
// Function to insert 'toggle content' button
102+
function insertToggleContentButton(element) {
103+
const initiallyVisible = window.innerWidth >= MIN_WINDOW_SIZE
104+
105+
const toggleContent = document.createElement('button');
106+
toggleContent.className = 'aws-toggle-content-btn';
107+
toggleContent.textContent = initiallyVisible ? '▼' : '▶'
108+
toggleContent.setAttribute('aria-expanded', initiallyVisible.toString());
109+
toggleContent.setAttribute('aria-label', 'Toggle code block for' + element.getAttribute("data-togglable"));
110+
toggleContent.setAttribute('aria-controls', element.id);
111+
112+
// Set initial visibility based on window size
113+
element.style.display = initiallyVisible ? 'block' : 'none'
114+
115+
// Toggle visibility onclick
116+
toggleContent.onclick = function() {
117+
const isExpanded = toggleContent.getAttribute('aria-expanded') === 'true';
118+
toggleContent.setAttribute('aria-expanded', (!isExpanded).toString());
119+
element.style.display = isExpanded ? 'none' : 'block'
120+
toggleContent.textContent = isExpanded ? '▶' : '▼'
121+
};
122+
123+
element.parentNode.insertBefore(toggleContent, element);
124+
}
125+
126+
document.querySelectorAll('.content[data-togglable]').forEach(insertToggleContentButton);
127+
128+
// Update content visibility on resize
129+
window.addEventListener('resize', function() {
130+
document.querySelectorAll('.content[data-togglable]').forEach(function(element) {
131+
const toggleContent = element.previousSibling;
132+
if (window.innerWidth < MIN_WINDOW_SIZE) {
133+
element.style.display = 'none';
134+
toggleContent.setAttribute('aria-expanded', 'false');
135+
} else {
136+
element.style.display = 'block';
137+
toggleContent.setAttribute('aria-expanded', 'true');
138+
}
139+
});
140+
});
141+
}
142+
143+
document.addEventListener('DOMContentLoaded', ensureContentReflow)
144+
if (document.readyState === "interactive" || document.readyState === "complete" ) { ensureContentReflow() }

0 commit comments

Comments
 (0)