Skip to content

Commit

Permalink
Fix keyboard access for scrollable regions created by notebook outputs
Browse files Browse the repository at this point in the history
  • Loading branch information
gabalafou committed Apr 25, 2024
1 parent a4eaf77 commit 221d4ec
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 11 deletions.
51 changes: 40 additions & 11 deletions src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -693,19 +693,45 @@ function setupMobileSidebarKeyboardHandlers() {
}

/**
* When the page loads or the window resizes check all elements with
* [data-tabindex="0"], and if they have scrollable overflow, set tabIndex = 0.
* When the page loads, or the window resizes, or descendant nodes are added or
* removed from the main element, check all code blocks and Jupyter notebook
* outputs, and for each one that has scrollable overflow, set tabIndex = 0.
*/
function setupLiteralBlockTabStops() {
function addTabStopsToScrollableElements() {
const updateTabStops = () => {
document.querySelectorAll('[data-tabindex="0"]').forEach((el) => {
el.tabIndex =
el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight
? 0
: -1;
});
document
.querySelectorAll(
'[data-tabindex="0"], ' + // code blocks
".output_area, " + // NBSphinx notebook output
".output, " + // Myst-NB
".jp-RenderedHTMLCommon" // ipywidgets
)
.forEach((el) => {
el.tabIndex =
el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight
? 0
: -1;
});
};
window.addEventListener("resize", debounce(updateTabStops, 300));
const debouncedUpdateTabStops = debounce(updateTabStops, 300);

// On window resize
window.addEventListener("resize", debouncedUpdateTabStops);

// The following MutationObserver is for ipywidgets, which take some time to
// finish loading and rendering on the page (so even after the "load" event is
// fired, they still have not finished rendering). Would be nice to replace
// the MutationObserver if there is a way to hook into the ipywidgets code to
// know when it is done.
const mainObserver = new MutationObserver(debouncedUpdateTabStops);

// On descendant nodes added/removed from main element
mainObserver.observe(document.getElementById("main-content"), {
subtree: true,
childList: true,
});

// On page load
updateTabStops();
}
function debounce(callback, wait) {
Expand All @@ -729,4 +755,7 @@ documentReady(setupSearchButtons);
documentReady(initRTDObserver);
documentReady(setupMobileSidebarKeyboardHandlers);
documentReady(fixMoreLinksInMobileSidebar);
documentReady(setupLiteralBlockTabStops);

// Use load event because determining whether an element has scrollable content
// depends on stylesheets (which come after DOMContentLoaded)
window.addEventListener("load", addTabStopsToScrollableElements);
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
html div.rendered_html,
// NBsphinx ipywidgets output selector
html .jp-RenderedHTMLCommon {
// Add some margin around the element box for the focus ring. Otherwise the
// focus ring gets clipped because the containing elements have `overflow:
// hidden` applied to them (via the `.lm-Widget` selector)
margin: $focus-ring-width;

table {
table-layout: auto;
}
Expand Down

0 comments on commit 221d4ec

Please sign in to comment.