Skip to content

Commit

Permalink
Only make scrollable code blocks into tab stops (#1777)
Browse files Browse the repository at this point in the history
* Only make scrollable code blocks into tab stops

* Check for y-overflow too
  • Loading branch information
gabalafou authored Apr 24, 2024
1 parent a78a066 commit a4eaf77
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 2 deletions.
27 changes: 27 additions & 0 deletions src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,32 @@ 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.
*/
function setupLiteralBlockTabStops() {
const updateTabStops = () => {
document.querySelectorAll('[data-tabindex="0"]').forEach((el) => {
el.tabIndex =
el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight
? 0
: -1;
});
};
window.addEventListener("resize", debounce(updateTabStops, 300));
updateTabStops();
}
function debounce(callback, wait) {
let timeoutId = null;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
callback(...args);
}, wait);
};
}

/*******************************************************************************
* Call functions after document loading.
*/
Expand All @@ -703,3 +729,4 @@ documentReady(setupSearchButtons);
documentReady(initRTDObserver);
documentReady(setupMobileSidebarKeyboardHandlers);
documentReady(fixMoreLinksInMobileSidebar);
documentReady(setupLiteralBlockTabStops);
4 changes: 2 additions & 2 deletions src/pydata_sphinx_theme/translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def starttag(self, *args, **kwargs):
kwargs["ARIA-LEVEL"] = "2"

if "pre" in args:
kwargs["tabindex"] = "0"
kwargs["data-tabindex"] = "0"

return super().starttag(*args, **kwargs)

Expand All @@ -50,7 +50,7 @@ def visit_literal_block(self, node):
# 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("<pre", '<pre tabindex="0"')
self.body[-1] = html_string.replace("<pre", '<pre data-tabindex="0"')
raise nodes.SkipNode

def visit_table(self, node):
Expand Down
22 changes: 22 additions & 0 deletions tests/test_a11y.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,25 @@ def test_version_switcher_highlighting(page: Page, url_base: str) -> None:
light_mode = "rgb(10, 125, 145)" # pst-color-primary
# dark_mode = "rgb(63, 177, 197)"
expect(entry).to_have_css("color", light_mode)


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})
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"
)

# Viewport is wide, so code block content fits, no overflow, no tab stop
assert code_block.evaluate("el => el.scrollWidth > el.clientWidth") is False
assert code_block.evaluate("el => el.tabIndex") != 0

page.set_viewport_size({"width": 400, "height": 720})

# Resize handler is debounced with 300 ms wait time
page.wait_for_timeout(301)

# 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

0 comments on commit a4eaf77

Please sign in to comment.