Skip to content

Commit 221d4ec

Browse files
committed
Fix keyboard access for scrollable regions created by notebook outputs
1 parent a4eaf77 commit 221d4ec

File tree

2 files changed

+45
-11
lines changed

2 files changed

+45
-11
lines changed

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

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -693,19 +693,45 @@ function setupMobileSidebarKeyboardHandlers() {
693693
}
694694

695695
/**
696-
* When the page loads or the window resizes check all elements with
697-
* [data-tabindex="0"], and if they have scrollable overflow, set tabIndex = 0.
696+
* When the page loads, or the window resizes, or descendant nodes are added or
697+
* removed from the main element, check all code blocks and Jupyter notebook
698+
* outputs, and for each one that has scrollable overflow, set tabIndex = 0.
698699
*/
699-
function setupLiteralBlockTabStops() {
700+
function addTabStopsToScrollableElements() {
700701
const updateTabStops = () => {
701-
document.querySelectorAll('[data-tabindex="0"]').forEach((el) => {
702-
el.tabIndex =
703-
el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight
704-
? 0
705-
: -1;
706-
});
702+
document
703+
.querySelectorAll(
704+
'[data-tabindex="0"], ' + // code blocks
705+
".output_area, " + // NBSphinx notebook output
706+
".output, " + // Myst-NB
707+
".jp-RenderedHTMLCommon" // ipywidgets
708+
)
709+
.forEach((el) => {
710+
el.tabIndex =
711+
el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight
712+
? 0
713+
: -1;
714+
});
707715
};
708-
window.addEventListener("resize", debounce(updateTabStops, 300));
716+
const debouncedUpdateTabStops = debounce(updateTabStops, 300);
717+
718+
// On window resize
719+
window.addEventListener("resize", debouncedUpdateTabStops);
720+
721+
// The following MutationObserver is for ipywidgets, which take some time to
722+
// finish loading and rendering on the page (so even after the "load" event is
723+
// fired, they still have not finished rendering). Would be nice to replace
724+
// the MutationObserver if there is a way to hook into the ipywidgets code to
725+
// know when it is done.
726+
const mainObserver = new MutationObserver(debouncedUpdateTabStops);
727+
728+
// On descendant nodes added/removed from main element
729+
mainObserver.observe(document.getElementById("main-content"), {
730+
subtree: true,
731+
childList: true,
732+
});
733+
734+
// On page load
709735
updateTabStops();
710736
}
711737
function debounce(callback, wait) {
@@ -729,4 +755,7 @@ documentReady(setupSearchButtons);
729755
documentReady(initRTDObserver);
730756
documentReady(setupMobileSidebarKeyboardHandlers);
731757
documentReady(fixMoreLinksInMobileSidebar);
732-
documentReady(setupLiteralBlockTabStops);
758+
759+
// Use load event because determining whether an element has scrollable content
760+
// depends on stylesheets (which come after DOMContentLoaded)
761+
window.addEventListener("load", addTabStopsToScrollableElements);

src/pydata_sphinx_theme/assets/styles/extensions/_notebooks.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
html div.rendered_html,
1313
// NBsphinx ipywidgets output selector
1414
html .jp-RenderedHTMLCommon {
15+
// Add some margin around the element box for the focus ring. Otherwise the
16+
// focus ring gets clipped because the containing elements have `overflow:
17+
// hidden` applied to them (via the `.lm-Widget` selector)
18+
margin: $focus-ring-width;
19+
1520
table {
1621
table-layout: auto;
1722
}

0 commit comments

Comments
 (0)