From 221d4ecc2b28bea3ace125966c8ebd8a8416f3cd Mon Sep 17 00:00:00 2001 From: gabalafou Date: Thu, 25 Apr 2024 13:11:19 +0200 Subject: [PATCH 01/17] Fix keyboard access for scrollable regions created by notebook outputs --- .../assets/scripts/pydata-sphinx-theme.js | 51 +++++++++++++++---- .../assets/styles/extensions/_notebooks.scss | 5 ++ 2 files changed, 45 insertions(+), 11 deletions(-) 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 cc97219b6..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,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) { @@ -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); 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; } From 008cc9acc3af5a3cf9156ca23a282359608360aa Mon Sep 17 00:00:00 2001 From: gabalafou Date: Fri, 26 Apr 2024 13:12:41 +0200 Subject: [PATCH 02/17] Remove data-tabindex, add test --- .../assets/scripts/pydata-sphinx-theme.js | 18 ++++++----- src/pydata_sphinx_theme/translator.py | 21 ------------- tests/test_a11y.py | 31 ++++++++++++++++++- 3 files changed, 40 insertions(+), 30 deletions(-) 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 20c002dd6..5f86842cb 100644 --- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js +++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js @@ -701,9 +701,9 @@ function addTabStopsToScrollableElements() { const updateTabStops = () => { document .querySelectorAll( - '[data-tabindex="0"], ' + // code blocks - ".output_area, " + // NBSphinx notebook output - ".output, " + // Myst-NB + "pre, " + // code blocks + ".nboutput > .output_area, " + // NBSphinx notebook output + ".cell_output > .output, " + // Myst-NB ".jp-RenderedHTMLCommon" // ipywidgets ) .forEach((el) => { @@ -743,11 +743,17 @@ function debounce(callback, wait) { }, wait); }; } +// Determining whether an element has scrollable content depends on stylesheets, +// so we're checking for the "load" event rather than "DOMContentLoaded" +if (document.readyState === "complete") { + addTabStopsToScrollableElements(); +} else { + window.addEventListener("load", addTabStopsToScrollableElements); +} /******************************************************************************* * Call functions after document loading. */ - documentReady(addModeListener); documentReady(scrollToActive); documentReady(addTOCInteractivity); @@ -755,7 +761,3 @@ documentReady(setupSearchButtons); documentReady(initRTDObserver); documentReady(setupMobileSidebarKeyboardHandlers); documentReady(fixMoreLinksInMobileSidebar); - -// 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/translator.py b/src/pydata_sphinx_theme/translator.py index e8ab9ea6e..88f79d48d 100644 --- a/src/pydata_sphinx_theme/translator.py +++ b/src/pydata_sphinx_theme/translator.py @@ -3,7 +3,6 @@ import types import sphinx -from docutils import nodes from packaging.version import Version from sphinx.application import Sphinx from sphinx.ext.autosummary import autosummary_table @@ -27,32 +26,12 @@ def starttag(self, *args, **kwargs): """Perform small modifications to tags. - ensure aria-level is set for any tag with heading role - - ensure
 tags have tabindex="0".
         """
         if kwargs.get("ROLE") == "heading" and "ARIA-LEVEL" not in kwargs:
             kwargs["ARIA-LEVEL"] = "2"
 
-        if "pre" in args:
-            kwargs["data-tabindex"] = "0"
-
         return super().starttag(*args, **kwargs)
 
-    def visit_literal_block(self, node):
-        """Modify literal blocks.
-
-        - add tabindex="0" to 
 tags within the HTML tree of the literal
-          block
-        """
-        try:
-            super().visit_literal_block(node)
-        except nodes.SkipNode:
-            # If the super method raises nodes.SkipNode, then we know it
-            # executed successfully and appended to self.body a string of HTML
-            # representing the code block, which we then modify.
-            html_string = self.body[-1]
-            self.body[-1] = html_string.replace(" None:
     """Code blocks that have scrollable content should be tab stops."""
     page.set_viewport_size({"width": 1440, "height": 720})
     page.goto(urljoin(url_base, "/examples/kitchen-sink/blocks.html"))
+
     code_block = page.locator(
-        'css=#code-block pre[data-tabindex="0"]', has_text="from typing import Iterator"
+        "css=#code-block pre", has_text="from typing import Iterator"
     )
 
     # Viewport is wide, so code block content fits, no overflow, no tab stop
@@ -262,3 +263,31 @@ def test_code_block_tab_stop(page: Page, url_base: str) -> None:
     # Narrow viewport, content overflows and code block should be a tab stop
     assert code_block.evaluate("el => el.scrollWidth > el.clientWidth") is True
     assert code_block.evaluate("el => el.tabIndex") == 0
+
+
+def test_notebook_output_tab_stop(page: Page, url_base: str) -> None:
+    """Notebook outputs that have scrollable content should be tab stops."""
+    page.goto(urljoin(url_base, "/examples/pydata.html"))
+
+    # A "plain" notebook output
+    nb_output = page.locator("css=#Pandas > .nboutput > .output_area")
+
+    # At the default viewport size (1280 x 720) the Pandas data table has
+    # overflow
+    assert nb_output.evaluate("el => el.scrollWidth > el.clientWidth") is True
+    assert nb_output.evaluate("el => el.tabIndex") == 0
+
+    # TODO: uncomment the following test code after #1760 is merged
+
+    # # An ipywidget notebook output
+    # ipywidget = page.locator("css=.jp-RenderedHTMLCommon").first
+
+    # # As soon as the ipywidget is attached to the page it should trigger the
+    # # mutation observer, which has a 300 ms debounce
+    # ipywidget.wait_for(state="attached")
+    # page.wait_for_timeout(301)
+
+    # # At the default viewport size (1280 x 720) the data table inside the
+    # # ipywidget has overflow
+    # assert ipywidget.evaluate("el => el.scrollWidth > el.clientWidth") is True
+    # assert ipywidget.evaluate("el => el.tabIndex") == 0

From 6ac0dbb0cf86fd5d373409df6317349df906e219 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 21 May 2024 08:33:33 +0000
Subject: [PATCH 03/17] [pre-commit.ci] Automatic linting and formatting fixes

---
 src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 3173193b5..d2b94070a 100644
--- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
+++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
@@ -704,7 +704,7 @@ function addTabStopsToScrollableElements() {
         "pre, " + // code blocks
           ".nboutput > .output_area, " + // NBSphinx notebook output
           ".cell_output > .output, " + // Myst-NB
-          ".jp-RenderedHTMLCommon" // ipywidgets
+          ".jp-RenderedHTMLCommon", // ipywidgets
       )
       .forEach((el) => {
         el.tabIndex =

From f749d8baa0ceff563f5a4a3281b53c982f292750 Mon Sep 17 00:00:00 2001
From: M Bussonnier 
Date: Wed, 22 May 2024 14:52:09 +0200
Subject: [PATCH 04/17] mark as failing test instead of comment

---
 tests/test_a11y.py | 29 ++++++++++++++++++-----------
 1 file changed, 18 insertions(+), 11 deletions(-)

diff --git a/tests/test_a11y.py b/tests/test_a11y.py
index 0f0862e9f..146513f3d 100644
--- a/tests/test_a11y.py
+++ b/tests/test_a11y.py
@@ -280,17 +280,24 @@ def test_notebook_output_tab_stop(page: Page, url_base: str) -> None:
     assert nb_output.evaluate("el => el.scrollWidth > el.clientWidth") is True
     assert nb_output.evaluate("el => el.tabIndex") == 0
 
-    # TODO: uncomment the following test code after #1760 is merged
 
-    # # An ipywidget notebook output
-    # ipywidget = page.locator("css=.jp-RenderedHTMLCommon").first
+@pytest.mark.fail(reason="fail until #1760 is merged")
+def test_notebook_output_tab_stop_1760(page: Page, url_base: str) -> None:
+    """# TODO: this was part of test_notebook_output_tab_stop.
 
-    # # As soon as the ipywidget is attached to the page it should trigger the
-    # # mutation observer, which has a 300 ms debounce
-    # ipywidget.wait_for(state="attached")
-    # page.wait_for_timeout(301)
+    I is now separated into it's own failing test until #1760 is merged.
+    """
+    page.goto(urljoin(url_base, "/examples/pydata.html"))
+
+    # An ipywidget notebook output
+    ipywidget = page.locator("css=.jp-RenderedHTMLCommon").first
+
+    # As soon as the ipywidget is attached to the page it should trigger the
+    # mutation observer, which has a 300 ms debounce
+    ipywidget.wait_for(state="attached")
+    page.wait_for_timeout(301)
 
-    # # At the default viewport size (1280 x 720) the data table inside the
-    # # ipywidget has overflow
-    # assert ipywidget.evaluate("el => el.scrollWidth > el.clientWidth") is True
-    # assert ipywidget.evaluate("el => el.tabIndex") == 0
+    # At the default viewport size (1280 x 720) the data table inside the
+    # ipywidget has overflow
+    assert ipywidget.evaluate("el => el.scrollWidth > el.clientWidth") is True
+    assert ipywidget.evaluate("el => el.tabIndex") == 0

From 8037ae3f9cbd2da2bbad6d6b3bc88c590dbd4e90 Mon Sep 17 00:00:00 2001
From: M Bussonnier 
Date: Thu, 23 May 2024 01:28:29 -0700
Subject: [PATCH 05/17] Update tests/test_a11y.py

---
 tests/test_a11y.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/test_a11y.py b/tests/test_a11y.py
index 146513f3d..68894e0c2 100644
--- a/tests/test_a11y.py
+++ b/tests/test_a11y.py
@@ -281,7 +281,7 @@ def test_notebook_output_tab_stop(page: Page, url_base: str) -> None:
     assert nb_output.evaluate("el => el.tabIndex") == 0
 
 
-@pytest.mark.fail(reason="fail until #1760 is merged")
+@pytest.mark.xfail(reason="fail until #1760 is merged", strict=True)
 def test_notebook_output_tab_stop_1760(page: Page, url_base: str) -> None:
     """# TODO: this was part of test_notebook_output_tab_stop.
 

From fe1c837996f7068d149b93a9249af4ac086c2c41 Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Thu, 23 May 2024 03:34:32 -0500
Subject: [PATCH 06/17] update comments

---
 tests/test_a11y.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/tests/test_a11y.py b/tests/test_a11y.py
index 68894e0c2..9ddc7b49f 100644
--- a/tests/test_a11y.py
+++ b/tests/test_a11y.py
@@ -276,8 +276,9 @@ def test_notebook_output_tab_stop(page: Page, url_base: str) -> None:
     nb_output = page.locator("css=#Pandas > .nboutput > .output_area")
 
     # At the default viewport size (1280 x 720) the Pandas data table has
-    # overflow
+    # overflow...
     assert nb_output.evaluate("el => el.scrollWidth > el.clientWidth") is True
+    # ... and so our js code on the page should make it keyboard-focusable (tabIndex = 0)
     assert nb_output.evaluate("el => el.tabIndex") == 0
 
 
@@ -298,6 +299,7 @@ def test_notebook_output_tab_stop_1760(page: Page, url_base: str) -> None:
     page.wait_for_timeout(301)
 
     # At the default viewport size (1280 x 720) the data table inside the
-    # ipywidget has overflow
+    # ipywidget has overflow...
     assert ipywidget.evaluate("el => el.scrollWidth > el.clientWidth") is True
+    # ... and so our js code on the page should make it keyboard-focusable (tabIndex = 0)
     assert ipywidget.evaluate("el => el.tabIndex") == 0

From b7506027e2710e3382931221773a6b09f08660f5 Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Thu, 23 May 2024 03:35:38 -0500
Subject: [PATCH 07/17] Update tests/test_a11y.py

---
 tests/test_a11y.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/test_a11y.py b/tests/test_a11y.py
index 9ddc7b49f..f3b483f3a 100644
--- a/tests/test_a11y.py
+++ b/tests/test_a11y.py
@@ -286,7 +286,7 @@ def test_notebook_output_tab_stop(page: Page, url_base: str) -> None:
 def test_notebook_output_tab_stop_1760(page: Page, url_base: str) -> None:
     """# TODO: this was part of test_notebook_output_tab_stop.
 
-    I is now separated into it's own failing test until #1760 is merged.
+    It is now separated into its own failing test until #1760 is merged.
     """
     page.goto(urljoin(url_base, "/examples/pydata.html"))
 

From a8762a80b8ad06881d280a00618e33cc9e412134 Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Thu, 23 May 2024 06:58:10 -0400
Subject: [PATCH 08/17] Add CSS to allow scrolling of ipywidget (#1760)

This addresses one of the issues in #1740: missing horizontal scrollbar.

- Add CSS rule to allow scrolling
- Add ipywidgets example to the examples/pydata page
---
 pyproject.toml                                              | 1 +
 .../assets/styles/extensions/_pydata.scss                   | 6 ++++++
 2 files changed, 7 insertions(+)

diff --git a/pyproject.toml b/pyproject.toml
index e816865b6..1856c9e21 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -76,6 +76,7 @@ doc = [
   "nbsphinx",
   "ipyleaflet",
   "colorama",
+  "ipywidgets"
 ]
 test = ["pytest", "pytest-cov", "pytest-regressions"]
 dev = ["pyyaml", "pre-commit", "nox", "pydata-sphinx-theme[doc,test]"]
diff --git a/src/pydata_sphinx_theme/assets/styles/extensions/_pydata.scss b/src/pydata_sphinx_theme/assets/styles/extensions/_pydata.scss
index 94a1994cc..56ee5fbf4 100644
--- a/src/pydata_sphinx_theme/assets/styles/extensions/_pydata.scss
+++ b/src/pydata_sphinx_theme/assets/styles/extensions/_pydata.scss
@@ -6,3 +6,9 @@
 .xr-wrap[hidden] {
   display: block !important;
 }
+
+// ipywidgets
+.jp-OutputArea-output.lm-Widget {
+  // override overflow:hidden rule from Lumino (.lm-Widget) to allow scrolling
+  overflow: auto;
+}

From 2788f60057b63cc3871def1a4a9bd2cb917b4ac0 Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Thu, 25 Apr 2024 13:11:19 +0200
Subject: [PATCH 09/17] Fix keyboard access for scrollable regions created by
 notebook outputs

---
 .../assets/scripts/pydata-sphinx-theme.js                  | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

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 4cddced76..4d93a4c3a 100644
--- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
+++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
@@ -791,11 +791,14 @@ async function fetchRevealBannersTogether() {
 // Call this one first to kick off the network request for the version warning
 // and announcement banner data as early as possible.
 documentReady(fetchRevealBannersTogether);
+
 documentReady(addModeListener);
 documentReady(scrollToActive);
 documentReady(addTOCInteractivity);
 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);

From ab616e62b853923193f129c66e8343edd31e99d1 Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Fri, 26 Apr 2024 13:12:41 +0200
Subject: [PATCH 10/17] Remove data-tabindex, add test

---
 src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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 4d93a4c3a..786f64e1e 100644
--- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
+++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
@@ -680,7 +680,7 @@ function addTabStopsToScrollableElements() {
         "pre, " + // code blocks
           ".nboutput > .output_area, " + // NBSphinx notebook output
           ".cell_output > .output, " + // Myst-NB
-          ".jp-RenderedHTMLCommon", // ipywidgets
+          ".jp-RenderedHTMLCommon" // ipywidgets
       )
       .forEach((el) => {
         el.tabIndex =
@@ -788,7 +788,7 @@ async function fetchRevealBannersTogether() {
  * Call functions after document loading.
  */
 
-// Call this one first to kick off the network request for the version warning
+// Pass this one first to kick off the network request for the version warning
 // and announcement banner data as early as possible.
 documentReady(fetchRevealBannersTogether);
 

From e3b7dfc35437bd8d346311724403a5829ab321de Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Fri, 24 May 2024 16:39:17 +0200
Subject: [PATCH 11/17] mark tab stop tests as a11y

---
 tests/test_a11y.py | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/tests/test_a11y.py b/tests/test_a11y.py
index f3b483f3a..1ef16feb9 100644
--- a/tests/test_a11y.py
+++ b/tests/test_a11y.py
@@ -245,6 +245,7 @@ def test_version_switcher_highlighting(page: Page, url_base: str) -> None:
         expect(entry).to_have_css("color", light_mode)
 
 
+@pytest.mark.a11y
 def test_code_block_tab_stop(page: Page, url_base: str) -> None:
     """Code blocks that have scrollable content should be tab stops."""
     page.set_viewport_size({"width": 1440, "height": 720})
@@ -268,6 +269,7 @@ def test_code_block_tab_stop(page: Page, url_base: str) -> None:
     assert code_block.evaluate("el => el.tabIndex") == 0
 
 
+@pytest.mark.a11y
 def test_notebook_output_tab_stop(page: Page, url_base: str) -> None:
     """Notebook outputs that have scrollable content should be tab stops."""
     page.goto(urljoin(url_base, "/examples/pydata.html"))
@@ -278,16 +280,15 @@ def test_notebook_output_tab_stop(page: Page, url_base: str) -> None:
     # At the default viewport size (1280 x 720) the Pandas data table has
     # overflow...
     assert nb_output.evaluate("el => el.scrollWidth > el.clientWidth") is True
-    # ... and so our js code on the page should make it keyboard-focusable (tabIndex = 0)
-    assert nb_output.evaluate("el => el.tabIndex") == 0
 
+    # ...and so our js code on the page should make it keyboard-focusable
+    # (tabIndex = 0)
+    assert nb_output.evaluate("el => el.tabIndex") == 0
 
-@pytest.mark.xfail(reason="fail until #1760 is merged", strict=True)
-def test_notebook_output_tab_stop_1760(page: Page, url_base: str) -> None:
-    """# TODO: this was part of test_notebook_output_tab_stop.
 
-    It is now separated into its own failing test until #1760 is merged.
-    """
+@pytest.mark.a11y
+def test_notebook_ipywidget_output_tab_stop(page: Page, url_base: str) -> None:
+    """Notebook ipywidget outputs that have scrollable content should be tab stops."""
     page.goto(urljoin(url_base, "/examples/pydata.html"))
 
     # An ipywidget notebook output
@@ -301,5 +302,7 @@ def test_notebook_output_tab_stop_1760(page: Page, url_base: str) -> None:
     # At the default viewport size (1280 x 720) the data table inside the
     # ipywidget has overflow...
     assert ipywidget.evaluate("el => el.scrollWidth > el.clientWidth") is True
-    # ... and so our js code on the page should make it keyboard-focusable (tabIndex = 0)
+
+    # ...and so our js code on the page should make it keyboard-focusable
+    # (tabIndex = 0)
     assert ipywidget.evaluate("el => el.tabIndex") == 0

From a0dc755211873a6d68bd43500ed188c69e8b4516 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Fri, 24 May 2024 14:40:19 +0000
Subject: [PATCH 12/17] [pre-commit.ci] Automatic linting and formatting fixes

---
 src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 786f64e1e..bc2fc1500 100644
--- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
+++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
@@ -680,7 +680,7 @@ function addTabStopsToScrollableElements() {
         "pre, " + // code blocks
           ".nboutput > .output_area, " + // NBSphinx notebook output
           ".cell_output > .output, " + // Myst-NB
-          ".jp-RenderedHTMLCommon" // ipywidgets
+          ".jp-RenderedHTMLCommon", // ipywidgets
       )
       .forEach((el) => {
         el.tabIndex =

From 658e0992e32257e0fe71eeedca5e5ba8f3a61aa7 Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Fri, 24 May 2024 16:47:38 +0200
Subject: [PATCH 13/17] fix bad rebase

---
 .../assets/scripts/pydata-sphinx-theme.js                    | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

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 bc2fc1500..17010e0f7 100644
--- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
+++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
@@ -719,6 +719,7 @@ function debounce(callback, wait) {
     }, wait);
   };
 }
+
 // Determining whether an element has scrollable content depends on stylesheets,
 // so we're checking for the "load" event rather than "DOMContentLoaded"
 if (document.readyState === "complete") {
@@ -798,7 +799,3 @@ documentReady(addTOCInteractivity);
 documentReady(setupSearchButtons);
 documentReady(initRTDObserver);
 documentReady(setupMobileSidebarKeyboardHandlers);
-
-// Use load event because determining whether an element has scrollable content
-// depends on stylesheets (which come after DOMContentLoaded)
-window.addEventListener("load", addTabStopsToScrollableElements);

From b44c530e33b0fc11dd0120e5201078b07144b746 Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Fri, 24 May 2024 16:52:50 +0200
Subject: [PATCH 14/17] call addTabStops after more important stuff

---
 .../assets/scripts/pydata-sphinx-theme.js        | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

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 17010e0f7..69201ed8a 100644
--- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
+++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
@@ -720,14 +720,6 @@ function debounce(callback, wait) {
   };
 }
 
-// Determining whether an element has scrollable content depends on stylesheets,
-// so we're checking for the "load" event rather than "DOMContentLoaded"
-if (document.readyState === "complete") {
-  addTabStopsToScrollableElements();
-} else {
-  window.addEventListener("load", addTabStopsToScrollableElements);
-}
-
 /*******************************************************************************
  * Announcement banner - fetch and load remote HTML
  */
@@ -799,3 +791,11 @@ documentReady(addTOCInteractivity);
 documentReady(setupSearchButtons);
 documentReady(initRTDObserver);
 documentReady(setupMobileSidebarKeyboardHandlers);
+
+// Determining whether an element has scrollable content depends on stylesheets,
+// so we're checking for the "load" event rather than "DOMContentLoaded"
+if (document.readyState === "complete") {
+  addTabStopsToScrollableElements();
+} else {
+  window.addEventListener("load", addTabStopsToScrollableElements);
+}

From a4b8b79fb1b8569c56b18a01d77251e90f92ed6c Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Fri, 24 May 2024 09:53:44 -0500
Subject: [PATCH 15/17] Update
 src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js

---
 src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 69201ed8a..c08a1dfb7 100644
--- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
+++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
@@ -707,7 +707,7 @@ function addTabStopsToScrollableElements() {
     childList: true,
   });
 
-  // On page load
+  // On page load (or whenever this function gets called)
   updateTabStops();
 }
 function debounce(callback, wait) {

From e9ca671941b64575abbaca14faa066c1db6a109f Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Fri, 24 May 2024 09:54:27 -0500
Subject: [PATCH 16/17] Update
 src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js

---
 src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 c08a1dfb7..2e7b7b59c 100644
--- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
+++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
@@ -707,7 +707,7 @@ function addTabStopsToScrollableElements() {
     childList: true,
   });
 
-  // On page load (or whenever this function gets called)
+  // On page load (when this function gets called)
   updateTabStops();
 }
 function debounce(callback, wait) {

From a583f6d494d1ab93a52bfde7ae9755dfb99b5533 Mon Sep 17 00:00:00 2001
From: gabalafou 
Date: Fri, 24 May 2024 09:56:35 -0500
Subject: [PATCH 17/17] Update
 src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js

---
 src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 2e7b7b59c..5a28b9405 100644
--- a/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
+++ b/src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
@@ -781,7 +781,7 @@ async function fetchRevealBannersTogether() {
  * Call functions after document loading.
  */
 
-// Pass this one first to kick off the network request for the version warning
+// This one first to kick off the network request for the version warning
 // and announcement banner data as early as possible.
 documentReady(fetchRevealBannersTogether);