Skip to content
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

Closed
wants to merge 3 commits into from
Closed

make code-block focusable #1104

wants to merge 3 commits into from

Conversation

12rambau
Copy link
Collaborator

@12rambau 12rambau commented Jan 7, 2023

Fix #1100

That was way more complicated than expected.

3 possible choices are available:

  • use pure JS to add the tabindex attributes at run time
  • parse the page and search for "pre" elements
  • override the visit_literal_block method

By 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 the div including the code elements and the pre 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

@12rambau 12rambau marked this pull request as ready for review January 7, 2023 14:33
@choldgraf
Copy link
Collaborator

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

@12rambau
Copy link
Collaborator Author

12rambau commented Jan 7, 2023

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 ?

I personally find over-writing the HTML writing methods to have created a lot of unexpected confusion over the years

The modifications I made here are just the extra attribute, the rest is coming from the docutils/sphinx code. I would love to use super but the method are not returning anything so you can't "add" on top of them. Also the overwriting issue may be less of a burden when with the #1105 ?

@12rambau
Copy link
Collaborator Author

12rambau commented Jan 8, 2023

@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 pst-atts and pst-class members. so the overrides become super simple (as we add parameters in members that are not read by other html5 builders) and we mostly rely on the "inherited" visit methods.

It simplifies a lot the overwrite of visit_literal_block and I think could be useful in the more general #1024

Copy link
Collaborator

@choldgraf choldgraf left a 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)
Copy link
Collaborator

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

Copy link
Collaborator Author

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

Copy link
Collaborator

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)

Copy link
Collaborator Author

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

Copy link
Collaborator

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
Copy link
Collaborator

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", {})
Copy link
Collaborator

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?

Copy link
Collaborator Author

@12rambau 12rambau Jan 9, 2023

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)

Copy link
Collaborator

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?

Copy link
Collaborator Author

@12rambau 12rambau Jan 9, 2023

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.

@12rambau 12rambau marked this pull request as draft January 9, 2023 06:49
@12rambau
Copy link
Collaborator Author

12rambau commented Jan 9, 2023

I converted it to draft as there is an unwanted behaviour: all the following tags have become focusable as well.

@trallard
Copy link
Collaborator

trallard commented Jan 9, 2023

The pre elements do seem to be focusable now, but I can indeed see other items that should not be focusable, for example, this p in the Kitchen sink -> blocks section

Blocks_—_PyData_Theme_0_12_1rc1_dev0_documentation

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

@trallard
Copy link
Collaborator

I thought I replied again.

@12rambau I looked at your PR - and it seems the previous approach where you were overwriting the highlight tags had the desired behaviour.

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

@gabalafou
Copy link
Collaborator

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 ?

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 tabindex="0" in the generated HTML source code for all overflow: auto elements (such as pre), for many things accessibility-related, a partially working solution is better than no solution at all.

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);

@gabalafou
Copy link
Collaborator

That said, I also echo @choldgraf's opinion that this should ultimately be fixed upstream

@gabalafou
Copy link
Collaborator

@trallard trallard added the tag: accessibility Issues related to accessibility issues or efforts label Feb 23, 2024
@gabalafou
Copy link
Collaborator

Since #1636 was merged, I'm going to close this PR, but please feel free to re-open if I am mistaken.

@gabalafou gabalafou closed this Apr 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tag: accessibility Issues related to accessibility issues or efforts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ENH - Make code blocks focusable
5 participants