diff --git a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js index 586ec2ef5..20c002dd6 100644 --- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js +++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js @@ -693,10 +693,11 @@ function setupMobileSidebarKeyboardHandlers() { } /** - * When the page loads or the window resizes check code blocks and Jupyter - * notebook outputs, 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( @@ -712,7 +713,25 @@ function setupLiteralBlockTabStops() { : -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) { @@ -736,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); diff --git a/src/pydata_sphinx_theme/assets/styles/extensions/_notebooks.scss b/src/pydata_sphinx_theme/assets/styles/extensions/_notebooks.scss index ea25ef1e8..4d39211f8 100644 --- a/src/pydata_sphinx_theme/assets/styles/extensions/_notebooks.scss +++ b/src/pydata_sphinx_theme/assets/styles/extensions/_notebooks.scss @@ -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; }