Skip to content

Commit 84980e4

Browse files
committed
chore: update dependencies and enhance accessibility features
- Updated the 'serve' package version in package.json and package-lock.json for improved performance. - Removed unused 'xml2js' dependency to streamline the project. - Enhanced the Playwright configuration to better manage the web server setup for testing. - Improved accessibility in the language switcher and navigation menu by refining focus management and keyboard interactions. - Updated the back-to-top button to manage tabindex for better accessibility compliance.
1 parent 7cfae94 commit 84980e4

File tree

9 files changed

+222
-103
lines changed

9 files changed

+222
-103
lines changed

.github/workflows/accessibility.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,8 @@ jobs:
5050
- name: Build site
5151
run: npm run build
5252

53-
- name: Serve and test
54-
run: |
55-
npx serve docs/public -l tcp://localhost:1313 --no-clipboard &
56-
sleep 2
57-
npm run test:a11y
53+
- name: Run accessibility tests
54+
run: npm run test:a11y
5855

5956
- name: Upload report
6057
if: always()

assets/js/core/back-to-top.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ document.addEventListener("DOMContentLoaded", function () {
66
document.addEventListener("scroll", (e) => {
77
if (window.scrollY > 300) {
88
backToTop.classList.remove("hx:opacity-0");
9+
backToTop.removeAttribute("tabindex");
910
} else {
1011
backToTop.classList.add("hx:opacity-0");
12+
backToTop.setAttribute("tabindex", "-1");
1113
}
1214
});
1315
}

assets/js/core/lang.js

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,102 @@
11
(function () {
22
const languageSwitchers = document.querySelectorAll('.hextra-language-switcher');
3+
const closeSwitcher = (switcher, focusSwitcher = false) => {
4+
switcher.dataset.state = 'closed';
5+
switcher.setAttribute('aria-expanded', 'false');
6+
const optionsElement = switcher.nextElementSibling;
7+
optionsElement.classList.add('hx:hidden');
8+
if (focusSwitcher) {
9+
switcher.focus();
10+
}
11+
};
12+
13+
const openSwitcher = (switcher, focusTarget = "none") => {
14+
switcher.dataset.state = 'open';
15+
switcher.setAttribute('aria-expanded', 'true');
16+
const optionsElement = switcher.nextElementSibling;
17+
if (optionsElement.classList.contains('hx:hidden')) {
18+
toggleMenu(switcher);
19+
} else {
20+
resizeMenu(switcher);
21+
}
22+
23+
if (focusTarget !== "none") {
24+
const items = Array.from(optionsElement.querySelectorAll('[role="menuitem"]'));
25+
if (items.length > 0) {
26+
const target = focusTarget === "last" ? items[items.length - 1] : items[0];
27+
target.focus();
28+
}
29+
}
30+
};
331

432
languageSwitchers.forEach((switcher) => {
533
switcher.addEventListener('click', (e) => {
634
e.preventDefault();
735

8-
switcher.dataset.state = switcher.dataset.state === 'open' ? 'closed' : 'open';
9-
switcher.setAttribute('aria-expanded', switcher.dataset.state === 'open' ? 'true' : 'false');
36+
if (switcher.dataset.state === 'open') {
37+
closeSwitcher(switcher);
38+
} else {
39+
openSwitcher(switcher);
40+
}
41+
});
1042

11-
toggleMenu(switcher);
43+
switcher.addEventListener('keydown', (e) => {
44+
if (e.key === 'ArrowDown') {
45+
e.preventDefault();
46+
openSwitcher(switcher, 'first');
47+
} else if (e.key === 'ArrowUp') {
48+
e.preventDefault();
49+
openSwitcher(switcher, 'last');
50+
}
51+
});
52+
});
53+
54+
document.querySelectorAll('.hextra-language-options[role=menu]').forEach((menu) => {
55+
menu.addEventListener('keydown', (e) => {
56+
const items = Array.from(menu.querySelectorAll('[role="menuitem"]'));
57+
if (items.length === 0) return;
58+
59+
const currentIndex = items.indexOf(document.activeElement);
60+
let newIndex;
61+
62+
switch (e.key) {
63+
case 'ArrowDown':
64+
e.preventDefault();
65+
newIndex = (currentIndex + 1) % items.length;
66+
items[newIndex].focus();
67+
break;
68+
case 'ArrowUp':
69+
e.preventDefault();
70+
newIndex = (currentIndex - 1 + items.length) % items.length;
71+
items[newIndex].focus();
72+
break;
73+
case 'Home':
74+
e.preventDefault();
75+
items[0].focus();
76+
break;
77+
case 'End':
78+
e.preventDefault();
79+
items[items.length - 1].focus();
80+
break;
81+
case 'Escape': {
82+
e.preventDefault();
83+
const switcher = menu.previousElementSibling;
84+
if (switcher) {
85+
closeSwitcher(switcher, true);
86+
}
87+
break;
88+
}
89+
}
1290
});
1391
});
1492

15-
window.addEventListener("resize", () => languageSwitchers.forEach(resizeMenu))
93+
window.addEventListener("resize", () => languageSwitchers.forEach(resizeMenu));
1694

17-
// Dismiss language switcher when clicking outside
95+
// Dismiss language switcher when clicking outside.
1896
document.addEventListener('click', (e) => {
19-
if (e.target.closest('.hextra-language-switcher') === null) {
97+
if (!e.target.closest('.hextra-language-switcher') && !e.target.closest('.hextra-language-options')) {
2098
languageSwitchers.forEach((switcher) => {
21-
switcher.dataset.state = 'closed';
22-
switcher.setAttribute('aria-expanded', 'false');
23-
const optionsElement = switcher.nextElementSibling;
24-
optionsElement.classList.add('hx:hidden');
99+
closeSwitcher(switcher);
25100
});
26101
}
27102
});

assets/js/core/nav-menu.js

Lines changed: 95 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,124 @@
11
(function () {
22
const hiddenClass = "hx:hidden";
33
const dropdownToggles = document.querySelectorAll(".hextra-nav-menu-toggle");
4+
const closeDropdown = (toggle, focusToggle = false) => {
5+
toggle.dataset.state = "closed";
6+
toggle.setAttribute("aria-expanded", "false");
7+
const menuItemsElement = toggle.nextElementSibling;
8+
menuItemsElement.classList.add(hiddenClass);
9+
if (focusToggle) {
10+
toggle.focus();
11+
}
12+
};
13+
14+
const openDropdown = (toggle, focusTarget = "none") => {
15+
// Close all other dropdowns first.
16+
dropdownToggles.forEach((otherToggle) => {
17+
if (otherToggle !== toggle) {
18+
closeDropdown(otherToggle);
19+
}
20+
});
21+
22+
toggle.dataset.state = "open";
23+
toggle.setAttribute("aria-expanded", "true");
24+
const menuItemsElement = toggle.nextElementSibling;
25+
26+
// Position dropdown centered with toggle.
27+
menuItemsElement.style.position = "absolute";
28+
menuItemsElement.style.top = "100%";
29+
menuItemsElement.style.left = "50%";
30+
menuItemsElement.style.transform = "translateX(-50%)";
31+
menuItemsElement.style.zIndex = "1000";
32+
menuItemsElement.classList.remove(hiddenClass);
33+
34+
if (focusTarget !== "none") {
35+
const items = Array.from(menuItemsElement.querySelectorAll('[role="menuitem"]'));
36+
if (items.length > 0) {
37+
const target = focusTarget === "last" ? items[items.length - 1] : items[0];
38+
target.focus();
39+
}
40+
}
41+
};
442

543
dropdownToggles.forEach((toggle) => {
644
toggle.addEventListener("click", (e) => {
745
e.preventDefault();
846
e.stopPropagation();
947

10-
// Close all other dropdowns first
11-
dropdownToggles.forEach((otherToggle) => {
12-
if (otherToggle !== toggle) {
13-
otherToggle.dataset.state = "closed";
14-
otherToggle.setAttribute('aria-expanded', 'false');
15-
const otherMenuItems = otherToggle.nextElementSibling;
16-
otherMenuItems.classList.add(hiddenClass);
17-
}
18-
});
19-
20-
// Toggle current dropdown
48+
// Toggle current dropdown.
2149
const isOpen = toggle.dataset.state === "open";
22-
toggle.dataset.state = isOpen ? "closed" : "open";
23-
toggle.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
24-
const menuItemsElement = toggle.nextElementSibling;
50+
if (isOpen) {
51+
closeDropdown(toggle);
52+
} else {
53+
openDropdown(toggle);
54+
}
55+
});
2556

26-
if (!isOpen) {
27-
// Position dropdown centered with toggle
28-
menuItemsElement.style.position = "absolute";
29-
menuItemsElement.style.top = "100%";
30-
menuItemsElement.style.left = "50%";
31-
menuItemsElement.style.transform = "translateX(-50%)";
32-
menuItemsElement.style.zIndex = "1000";
57+
toggle.addEventListener("keydown", (e) => {
58+
if (e.key === "ArrowDown") {
59+
e.preventDefault();
60+
openDropdown(toggle, "first");
61+
} else if (e.key === "ArrowUp") {
62+
e.preventDefault();
63+
openDropdown(toggle, "last");
64+
}
65+
});
66+
});
3367

34-
// Show dropdown
35-
menuItemsElement.classList.remove(hiddenClass);
36-
} else {
37-
// Hide dropdown
38-
menuItemsElement.classList.add(hiddenClass);
68+
document.querySelectorAll(".hextra-nav-menu-items[role=menu]").forEach((menu) => {
69+
menu.addEventListener("keydown", (e) => {
70+
const items = Array.from(menu.querySelectorAll('[role="menuitem"]'));
71+
if (items.length === 0) return;
72+
73+
const currentIndex = items.indexOf(document.activeElement);
74+
let newIndex;
75+
76+
switch (e.key) {
77+
case "ArrowDown":
78+
e.preventDefault();
79+
newIndex = (currentIndex + 1) % items.length;
80+
items[newIndex].focus();
81+
break;
82+
case "ArrowUp":
83+
e.preventDefault();
84+
newIndex = (currentIndex - 1 + items.length) % items.length;
85+
items[newIndex].focus();
86+
break;
87+
case "Home":
88+
e.preventDefault();
89+
items[0].focus();
90+
break;
91+
case "End":
92+
e.preventDefault();
93+
items[items.length - 1].focus();
94+
break;
95+
case "Escape": {
96+
e.preventDefault();
97+
const toggle = menu.previousElementSibling;
98+
if (toggle) {
99+
closeDropdown(toggle, true);
100+
}
101+
break;
102+
}
39103
}
40104
});
41105
});
42106

43-
// Dismiss dropdown when clicking outside
107+
// Dismiss dropdown when clicking outside.
44108
document.addEventListener("click", (e) => {
45-
if (e.target.closest(".hextra-nav-menu-toggle") === null) {
109+
if (!e.target.closest(".hextra-nav-menu-toggle") && !e.target.closest(".hextra-nav-menu-items")) {
46110
dropdownToggles.forEach((toggle) => {
47-
toggle.dataset.state = "closed";
48-
toggle.setAttribute('aria-expanded', 'false');
49-
const menuItemsElement = toggle.nextElementSibling;
50-
menuItemsElement.classList.add(hiddenClass);
111+
closeDropdown(toggle);
51112
});
52113
}
53114
});
54115

55-
// Close dropdowns on escape key
116+
// Close dropdowns on escape key.
56117
document.addEventListener("keydown", (e) => {
57118
if (e.key === "Escape") {
58119
dropdownToggles.forEach((toggle) => {
59120
if (toggle.dataset.state === "open") {
60-
toggle.dataset.state = "closed";
61-
toggle.setAttribute('aria-expanded', 'false');
62-
const menuItemsElement = toggle.nextElementSibling;
63-
menuItemsElement.classList.add(hiddenClass);
64-
toggle.focus();
121+
closeDropdown(toggle, true);
65122
}
66123
});
67124
}

layouts/_partials/toc.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
<a class="hx:inline-block hx:rounded-sm hx:text-xs hx:font-medium hx:text-gray-500 hx:hover:text-gray-900 hx:dark:text-gray-400 hx:dark:hover:text-gray-100 hx:contrast-more:text-gray-800 hx:contrast-more:dark:text-gray-50 hx:hextra-focus-visible-inset" href="{{ $editURL }}" target="_blank" rel="noreferrer">{{ $editThisPage }}</a>
5252
{{- end -}}
5353
{{/* Scroll To Top */}}
54-
<button id="backToTop" onClick="scrollUp();" class="hx:cursor-pointer hx:transition-all hx:duration-75 hx:opacity-0 hx:text-xs hx:font-medium hx:text-gray-500 hx:hover:text-gray-900 hx:dark:text-gray-400 hx:dark:hover:text-gray-100 hx:contrast-more:text-gray-800 hx:contrast-more:dark:text-gray-50">
54+
<button id="backToTop" tabindex="-1" onClick="scrollUp();" class="hx:cursor-pointer hx:transition-all hx:duration-75 hx:opacity-0 hx:text-xs hx:font-medium hx:text-gray-500 hx:hover:text-gray-900 hx:dark:text-gray-400 hx:dark:hover:text-gray-100 hx:contrast-more:text-gray-800 hx:contrast-more:dark:text-gray-50">
5555
<span>
5656
{{- $backToTop -}}
5757
</span>

package-lock.json

Lines changed: 2 additions & 37 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
"postcss-cli": "^11.0.1",
1515
"prettier": "^3.8.0",
1616
"prettier-plugin-go-template": "^0.0.15",
17-
"serve": "^14.2.4",
18-
"tailwindcss": "^4.1.18",
19-
"xml2js": "^0.6.2"
17+
"serve": "^14.2.5",
18+
"tailwindcss": "^4.1.18"
2019
}
2120
}

0 commit comments

Comments
 (0)