-
-
Notifications
You must be signed in to change notification settings - Fork 32.7k
Fixed #36458 Move focus to admin widget popup window after button click #19632
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
base: main
Are you sure you want to change the base?
Conversation
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.
Hello! Thank you for your contribution 💪
As it's your first contribution be sure to check out the patch review checklist.
If you're fixing a ticket from Trac make sure to set the "Has patch" flag and include a link to this PR in the ticket!
If you have any design or process questions then you can ask in the Django forum.
Welcome aboard ⛵️!
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.
Thank you for picking up this ticket @XChaitanyaX ⭐️
I've added a couple of comments inline below.
@@ -179,6 +181,36 @@ | |||
DateTimeShortcuts.dismissClock(num); | |||
}); | |||
|
|||
// Handle tab navigation within the popup (after content is created) | |||
clock_box.addEventListener('keydown', function(event) { |
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.
The changes added are great, but it doesn't seem like screen readers were taken into consideration.
When using a screen reader, users can navigate specific elements using the Tab key.
The internal elements of the dialogs provided by the admin (such as Date and Time pickers) are implemented as <a>
tags, so they are navigable using the Tab key while a screen reader is active.
The current code added seems to be designed for navigation with a regular keyboard.
focusNextElement: function(currentElement) { | ||
// Get all tabbable elements in the document, excluding hidden popup elements | ||
const allTabbableElements = document.querySelectorAll( |
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 don’t think most of the changes are designed with screen readers in mind.
This is the same as my comment above.
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.
Sure thing, previously the Tab key is not even working as intended to (the users can still navigate but in a very awkward way i.e. to go through all the elements and then the time and date elements are focused which is annoying for the specific types of users) as the popup elements are not even focused when entered. And the Tab is now working (but not focusing to the current date or time as you mentioned below). Thank you for the comments, I will try to fix them.
popup.fix.mp4The video shows a small lag (which actually is not there when I tested it) when the clock-box is clicked (also at the calendar-box focusing the current date) to focus the Now element. |
@XChaitanyaX In other words, as it's currently implemented, it's not about enabling navigation to a specific area using the Tab key without a screen reader. ![]() When using a screen reader, users can navigate between elements using the Tab key or reader specific navigation commands, even without any special implementation on our side. The issue we need to address is ensuring that, when using a screen reader, pressing Enter on the date picker or time picker opens the dialog and allows immediate access to specific elements within it (as I pointed out in a previous comment). ++ If focus is set to a specific element, screen readers can immediately access that element. |
…and add keyboard navigation to the admin widgets
screen.reader.fix.mp4
|
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.
Thank you @XChaitanyaX .
I worked on the date picker separately.
index 8168172a97..73802c26b9 100644
--- a/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js
+++ b/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js
@@ -245,11 +245,13 @@
const cal_link = document.createElement('a');
cal_link.href = '#';
cal_link.id = DateTimeShortcuts.calendarLinkName + num;
- cal_link.addEventListener('click', function(e) {
- e.preventDefault();
- // avoid triggering the document click handler to dismiss the calendar
- e.stopPropagation();
- DateTimeShortcuts.openCalendar(num);
+ ['click', 'keydown'].forEach((event) => {
+ cal_link.addEventListener(event, function(e) {
+ e.preventDefault();
+ // avoid triggering the document click handler to dismiss the calendar
+ e.stopPropagation();
+ DateTimeShortcuts.openCalendar(num, e);
+ });
});
quickElement(
'span', cal_link, '',
@@ -345,7 +347,7 @@
}
});
},
- openCalendar: function(num) {
+ openCalendar: function(num, e) {
const cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1 + num);
const cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName + num);
const inp = DateTimeShortcuts.calendarInputs[num];
@@ -376,6 +378,11 @@
cal_box.style.top = Math.max(0, findPosY(cal_link) - 75) + 'px';
cal_box.style.display = 'block';
+ if (e.key === 'Enter') {
+ const selected = cal_box.querySelector('td.selected a');
+ const focusElement = selected ? selected : cal_box.querySelector('td.today a');
+ focusElement.focus();
+ }
document.addEventListener('click', DateTimeShortcuts.dismissCalendarFunc[num]);
},
dismissCalendar: function(num) {
When a screen reader user accesses the date picker element, the above code alone is sufficient to focus on a specific element (the selected date if one exists, or today if not), allowing them to freely navigate the dialogs.
I think this change is sufficient for improving the accessibility of the date picker in this ticket for now.
clock_box.tabIndex = -1; // Make focusable but not in tab order | ||
// Add ARIA attributes for better screen reader support | ||
clock_box.setAttribute('role', 'dialog'); | ||
clock_box.setAttribute('aria-label', gettext('Choose a time')); | ||
clock_box.setAttribute('aria-modal', 'true'); |
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.
Using aria attributes on dialog buttons is a good idea, but it seems like something that should be handled in a separate ticket from this one.
There is currently no ticket for improving the accessibility of the clock box, but there is one for the date picker.
https://code.djangoproject.com/ticket/36459
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.
Aria attributes:
- I think aria attributes are necessary and maybe some discussion is needed on it.
- Also, in my opinion it might be good if we resolve all the aria attributes for both time and date widget in the same place, rather than 2 separate tickets
And I am removing the aria attributes as we are having a separate ticket.
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 agree that the aria attributes should be added. However, as you mentioned, this should be handled separately from this ticket.
Since the two widgets have a similar structure, I also think it might be better to handle them together for the sake of consistency, but hmm... I guess that’s something to think about later. 😄
time_list.setAttribute('role', 'list'); | ||
time_list.setAttribute('aria-label', gettext('Time options')); |
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.
Same as the comment above, this applies to all other changes involving the addition of aria attributes as well.
django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js
Outdated
Show resolved
Hide resolved
Changes done
|
Static demo site if anyone wants to test this: Change session - static demo |
@XChaitanyaX |
++ When the picker is activated by pressing Enter without using a screen reader, the user still has to navigate through all the elements, just like a screen reader user would. However, I believe this improvement will address that issue for both cases. |
Yes, it will fix for both the cases
|
Yes, I think so too. The W3C example date picker also provides this functionality. |
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.
Thank you for the PR @XChaitanyaX ⭐
Note that we will need tests for the changes, this could either be a selenium test or a javascript test in js_tests/admin/DateTimeShortcuts.test.js
I have tested and the focus does go in the popup window of the datepicker, however:
- when I have tabbed through the options, I should loop within the datepicker. Instead I then tab through the top of the page
- when I hit "esc" I should be focused on the trigger button (and can continue to tab from their) but instead I am at the top of the page
See: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/#keyboardinteraction
'class', 'clock-icon', | ||
'title', gettext('Choose a Time') | ||
'class', 'clock-icon' |
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.
Note that if possible, only include changes that are relevant for the ticket
Other fixes should be included with other associated tickets (in a separate commit) 👍
@Antoliny0919 I wrote something like this and it failed
Died on test 1: Cannot read properties of null (reading 'offsetParent') |
|
Test looks good. index c47e0e3ec1..3a9ac3f9d9 100644
--- a/tests/admin_widgets/tests.py
+++ b/tests/admin_widgets/tests.py
@@ -1176,6 +1176,23 @@ class DateTimePickerSeleniumTests(AdminWidgetSeleniumTestCase):
# The right month and year are displayed.
self.wait_for_text("#calendarin0 caption", expected_caption)
+ def test_calendar_press_enter_focus_element(self):
+ from selenium.webdriver.common.by import By
+ from selenium.webdriver.common.keys import Keys
+
+ self.admin_login(username="super", password="secret", login_url="/")
+ self.selenium.get(
+ self.live_server_url + reverse("admin:admin_widgets_member_add")
+ )
+
+ icon = self.selenium.find_element(By.ID, "calendarlink0")
+ expected_focus_element = self.selenium.find_element(
+ By.CSS_SELECTOR, "div#calendarin0 table td.today a"
+ )
+ icon.send_keys(Keys.ENTER)
+ focused_element = self.selenium.switch_to.active_element
+ self.assertEqual(expected_focus_element, focused_element)
+ |
Should the tab navigation within the datepicker also be part of this work? |
The same is true of the clock widget. I think this ticket should ideally be scoped for keyboard navigation of widget popups 👍 |
So, the goals we should aim to achieve in this PR are as follows:
Thinking about it, implementing all of these features would likely require a significant amount of Javascript and test coverage. I believe we need a clear direction for what we want to achieve in this PR. |
I'm happy for the accessibility team to come up with a plan for the widgets |
Trac ticket number
ticket-36458
Branch description
The proposed change ensures that when a popup widget is triggered, keyboard focus is programmatically moved to the desired element (current date for the date picker and the 'Now' element in the time picker) within the popup.
Checklist
main
branch.