-
Notifications
You must be signed in to change notification settings - Fork 319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
make code-block focusable #1104
Conversation
Hmm - my initial thinking after taking a look is that this is complex to understand and will be tricky to maintain over time, and really should be fixed at the level of sphinx or docutils and not this theme. Regarding your points above, it seems like using JavaScript would be the least complex. I personally find over-writing the HTML writing methods to have created a lot of unexpected confusion over the years |
I droped the js solution because I though that relying on a JS script for a11y was not robust enough, but maybe I'm wrong @trallard do you have any insight for us ?
The modifications I made here are just the extra attribute, the rest is coming from the docutils/sphinx code. I would love to use |
@choldgraf so new implementation idea. Instead of overriding completely the visit methods (with the high risk of not staying up to date with Sphinx or docutils) I decided to take advantage of the flexibility of Python. Now the main override of the HTML5 builder is its capacity to add extra arguments based on the It simplifies a lot the overwrite of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks way simpler for sure :-) I left a few questions in there to try and make it clearer what we're doing but in general this looks good!
Maybe @trallard can confirm that this pattern of tabindex
will actually solve the original problem?
# update attributes using pst_atts | ||
if hasattr(self, "pst_atts"): | ||
for att, val in self.pst_atts.items(): | ||
kwargs[att] = kwargs.pop(att, val) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the same as:
if att not in kwargs:
kwargs[att] = val
?
If so, I feel like that is a bit easier to understand. But maybe I am mis-reading these lines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not exactly equivalent. here what I do is :
try to find the attribute: yes: pop it else use our value
In yours you always replace it even if it's already in kwargs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not exactly equivalent. here what I do is : try to find the attribute: yes: pop it else use our value
In yours you always replace it even if it's already in kwargs
No, check the if
condition. (Aside: another way to do this that avoids the conditional altogether is to use dict.setdefault
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad you are right. I'll have a look to setdefault
I want to avoid "if" as much as possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do find setdefault to be easier to quickly understand. Personally I find the "if" statement to be the least "idiosyncratic" and most straightforward to understand, but setdefault works too.
""" | ||
Perform small modification on specific tags: | ||
- ensure an aria-level is set for any heading role | ||
- include the pst_atts in the final attributes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a quick note of why atts and class are added / what they do?
"""overwrite the literal-block element to make them focusable""" | ||
|
||
# add tabindex to the node | ||
pst_atts = getattr(node, "pst_atts", {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so pst_atts are chunks of data we are adding to the object on our own in order to re-use them later? And I guess we can't just modify atts
and classes
directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
classes
and atts
are keyword arguments of the starttag
method. They are not reachable without a strong override of the visit
method (as I did in my first proposal). In this case the advantage is to carry the information within the node object allowing me to make very small changes to visit methods:
def visit_xxx(self, node):
node.pst_class = "toto tutu tata"
super().visit_xxx(node)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah wait so pst is not short for "pydata sphinx theme"? These are in-built sphinx things?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are very right "pst" stands for pydata-sphinx-theme which guarantee that no other lib should interfere with these values.
It's the cool thing about Python I can add as many member as I want even after init.
I converted it to draft as there is an unwanted behaviour: all the following tags have become focusable as well. |
The Among many other HTML elements (span)... I am not super familiar with the Sphinx/PyData parsers/HTML converters, so I will have to do some diving into how this works before being helpful |
I thought I replied again. @12rambau I looked at your PR - and it seems the previous approach where you were overwriting the Though I know there are concerns regarding overwriting the methods, so perhaps after #1105 this PR could be revisited to make the code blocks focusable only as opposed to all the literals as is currently in this PR |
I don't see that anyone responded to this point, so I'll add my two cents. While I agree that the better solution is to add I suspect that we could add the following line of JavaScript to every PyData-themed page and it would fix the issue in nearly all places for nearly all users: document.querySelectorAll('pre').forEach(el => el.tabIndex = 0); |
That said, I also echo @choldgraf's opinion that this should ultimately be fixed upstream |
Cross reference: Ensure code blocks (<pre>) are keyboard focusable #1636 |
Since #1636 was merged, I'm going to close this PR, but please feel free to re-open if I am mistaken. |
Fix #1100
That was way more complicated than expected.
3 possible choices are available:
visit_literal_block
methodBy looking more carefully at it I realized that not all the "pre" elements are actually generated by docutils (the one included in highlighted code for example are never parsed) so I decided to overwrite the
visit_literal_block
to get both thediv
including the code elements and thepre
from non highlighted elements.before:
https://pydata-sphinx-theme.readthedocs.io/en/stable/examples/kitchen-sink/blocks.html#code-block
after:
https://pydata-sphinx-theme--1104.org.readthedocs.build/en/1104/examples/kitchen-sink/blocks.html#literal-blocks