Skip to content

Commit a5bef7c

Browse files
committed
aria attributes do not update if user uses mouse instead of keyboard
1 parent e054da1 commit a5bef7c

File tree

2 files changed

+79
-7
lines changed

2 files changed

+79
-7
lines changed

src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,58 @@ if (hasVersionsJSON && (hasSwitcherMenu || wantsWarningBanner)) {
564564
}
565565
}
566566

567+
/*******************************************************************************
568+
* Table of contents expand/collapse chevron aka toctree-toggle
569+
*/
570+
function setupToctreeToggle() {
571+
const handleKey = (event) => {
572+
if (
573+
!event.target.classList.contains("toctree-toggle") ||
574+
(event.key !== "Enter" && event.key !== " ")
575+
) {
576+
return;
577+
}
578+
579+
// Prevent default in any case
580+
event.preventDefault();
581+
582+
if (
583+
!(
584+
// Enter key triggers button action on keydown
585+
(
586+
(event.type === "keydown" && event.key === "Enter") ||
587+
// Space key triggers button action on keyup
588+
(event.type === "keyup" && event.key === " ")
589+
)
590+
)
591+
) {
592+
return;
593+
}
594+
595+
event.stopPropagation();
596+
const label = event.target;
597+
const forId = label.getAttribute("for");
598+
const invisibleCheckbox = document.getElementById(forId);
599+
invisibleCheckbox.checked = !invisibleCheckbox.checked;
600+
if (label.getAttribute("aria-expanded") === "true") {
601+
label.setAttribute(
602+
"aria-label",
603+
label.ariaLabel.replace("Collapse", "Expand")
604+
);
605+
label.setAttribute("aria-expanded", "false");
606+
} else {
607+
label.setAttribute(
608+
"aria-label",
609+
label.ariaLabel.replace("Expand", "Collapse")
610+
);
611+
label.setAttribute("aria-expanded", "true");
612+
}
613+
};
614+
615+
window.addEventListener("keydown", handleKey);
616+
window.addEventListener("keyup", handleKey);
617+
}
618+
567619
/*******************************************************************************
568620
* Call functions after document loading.
569621
*/
@@ -572,4 +624,5 @@ documentReady(addModeListener);
572624
documentReady(scrollToActive);
573625
documentReady(addTOCInteractivity);
574626
documentReady(setupSearchButtons);
627+
documentReady(setupToctreeToggle);
575628
documentReady(initRTDObserver);

src/pydata_sphinx_theme/toctree.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,11 @@ def generate_header_nav_before_dropdown(n_links_before_dropdown):
9191
page = toc.attributes["parent"] if page == "self" else page
9292

9393
# If this is the active ancestor page, add a class so we highlight it
94-
current = " current active" if page == active_header_page else ""
94+
current = ""
95+
aria_current = ""
96+
if page == active_header_page:
97+
current = " current active"
98+
aria_current = ' aria-current="page"'
9599

96100
# sanitize page title for use in the html output if needed
97101
if title is None:
@@ -114,7 +118,7 @@ def generate_header_nav_before_dropdown(n_links_before_dropdown):
114118
# create the html output
115119
links_html.append(
116120
f"""
117-
<li class="nav-item{current}">
121+
<li class="nav-item{current}"{aria_current}>
118122
<a class="nav-link nav-{link_status}" href="{link_href}">
119123
{title}
120124
</a>
@@ -351,7 +355,7 @@ def add_collapse_checkboxes(soup: BeautifulSoup) -> None:
351355
"""Add checkboxes to collapse children in a toctree."""
352356
# based on https://github.com/pradyunsg/furo
353357

354-
toctree_checkbox_count = 0
358+
toctree_subtree_count = 0
355359

356360
for element in soup.find_all("li", recursive=True):
357361
# We check all "li" elements, to add a "current-page" to the correct li.
@@ -364,22 +368,35 @@ def add_collapse_checkboxes(soup: BeautifulSoup) -> None:
364368
parentli.select("p.caption ~ input")[0].attrs["checked"] = ""
365369

366370
# Nothing more to do, unless this has "children"
367-
if not element.find("ul"):
371+
subtree = element.find("ul")
372+
if not subtree:
368373
continue
369374

370375
# Add a class to indicate that this has children.
371376
element["class"] = classes + ["has-children"]
372377

373378
# We're gonna add a checkbox.
374-
toctree_checkbox_count += 1
375-
checkbox_name = f"toctree-checkbox-{toctree_checkbox_count}"
379+
toctree_subtree_count += 1
380+
subtree_name = f"toctree-subtree-{toctree_subtree_count}"
381+
subtree["id"] = subtree_name
382+
checkbox_name = f"toctree-checkbox-{toctree_subtree_count}"
376383

377384
# Add the "label" for the checkbox which will get filled.
378385
if soup.new_tag is None:
379386
continue
380387

388+
section_name = element.a.get_text()
381389
label = soup.new_tag(
382-
"label", attrs={"for": checkbox_name, "class": "toctree-toggle"}
390+
"label",
391+
attrs={
392+
"role": "button",
393+
"for": checkbox_name,
394+
"class": "toctree-toggle",
395+
"tabindex": "0",
396+
"aria-expanded": "false",
397+
"aria-controls": subtree_name,
398+
"aria-label": f"Expand section {section_name}",
399+
},
383400
)
384401
label.append(soup.new_tag("i", attrs={"class": "fa-solid fa-chevron-down"}))
385402
if "toctree-l0" in classes:
@@ -402,6 +419,8 @@ def add_collapse_checkboxes(soup: BeautifulSoup) -> None:
402419
# (by checking the checkbox)
403420
if "current" in classes:
404421
checkbox.attrs["checked"] = ""
422+
label.attrs["aria-expanded"] = "true"
423+
label.attrs["aria-label"] = f"Collapse section {section_name}"
405424

406425
element.insert(1, checkbox)
407426

0 commit comments

Comments
 (0)