-
Notifications
You must be signed in to change notification settings - Fork 326
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
Keyboard handlers to open and close mobile sidebars #1585
Changes from all commits
85badcb
920c91d
38ef477
9803c5e
e054da1
342a9ef
eb3d0a2
372021b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -562,6 +562,89 @@ if (hasVersionsJSON && (hasSwitcherMenu || wantsWarningBanner)) { | |
} | ||
} | ||
|
||
/******************************************************************************* | ||
* Add keyboard functionality to mobile sidebars. | ||
* | ||
* Wire up the hamburger-style buttons using the click event which (on buttons) | ||
* handles both mouse clicks and the space and enter keys. | ||
*/ | ||
function setupMobileSidebarKeyboardHandlers() { | ||
// These are hidden checkboxes at the top of the page whose :checked property | ||
// allows the mobile sidebars to be hidden or revealed via CSS. | ||
const primaryToggle = document.getElementById("pst-primary-sidebar-checkbox"); | ||
const secondaryToggle = document.getElementById( | ||
"pst-secondary-sidebar-checkbox" | ||
); | ||
const primarySidebar = document.querySelector(".bd-sidebar-primary"); | ||
const secondarySidebar = document.querySelector(".bd-sidebar-secondary"); | ||
|
||
// Toggle buttons - | ||
// | ||
// These are the hamburger-style buttons in the header nav bar. When the user | ||
// clicks, the button transmits the click to the hidden checkboxes used by the | ||
// CSS to control whether the sidebar is open or closed. | ||
const primaryClickTransmitter = document.querySelector(".primary-toggle"); | ||
const secondaryClickTransmitter = document.querySelector(".secondary-toggle"); | ||
[ | ||
[primaryClickTransmitter, primaryToggle, primarySidebar], | ||
[secondaryClickTransmitter, secondaryToggle, secondarySidebar], | ||
].forEach(([clickTransmitter, toggle, sidebar]) => { | ||
if (!clickTransmitter) { | ||
return; | ||
} | ||
clickTransmitter.addEventListener("click", (event) => { | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
toggle.checked = !toggle.checked; | ||
|
||
// If we are opening the sidebar, move focus to the first focusable item | ||
// in the sidebar | ||
if (toggle.checked) { | ||
// Note: this selector is not exhaustive, and we may need to update it | ||
// in the future | ||
const tabStop = sidebar.querySelector("a, button"); | ||
// use setTimeout because you cannot move focus synchronously during a | ||
// click in the handler for the click event | ||
setTimeout(() => tabStop.focus(), 100); | ||
} | ||
}); | ||
}); | ||
|
||
// Escape key - | ||
// | ||
// When sidebar is open, user should be able to press escape key to close the | ||
// sidebar. | ||
[ | ||
[primarySidebar, primaryToggle, primaryClickTransmitter], | ||
[secondarySidebar, secondaryToggle, secondaryClickTransmitter], | ||
].forEach(([sidebar, toggle, transmitter]) => { | ||
if (!sidebar) { | ||
return; | ||
} | ||
sidebar.addEventListener("keydown", (event) => { | ||
if (event.key === "Escape") { | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
toggle.checked = false; | ||
transmitter.focus(); | ||
} | ||
}); | ||
}); | ||
|
||
// When the <label> overlay is clicked to close the sidebar, return focus to | ||
// the opener button in the nav bar. | ||
Comment on lines
+634
to
+635
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah seems this refers to the |
||
[ | ||
[primaryToggle, primaryClickTransmitter], | ||
[secondaryToggle, secondaryClickTransmitter], | ||
].forEach(([toggle, transmitter]) => { | ||
toggle.addEventListener("change", (event) => { | ||
if (!event.currentTarget.checked) { | ||
transmitter.focus(); | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
/******************************************************************************* | ||
* Call functions after document loading. | ||
*/ | ||
|
@@ -571,3 +654,4 @@ documentReady(scrollToActive); | |
documentReady(addTOCInteractivity); | ||
documentReady(setupSearchButtons); | ||
documentReady(initRTDObserver); | ||
documentReady(setupMobileSidebarKeyboardHandlers); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,20 +28,22 @@ label.overlay { | |
|
||
input { | ||
// Show the correct overlay when its input is checked | ||
&#__primary:checked + label.overlay.overlay-primary, | ||
&#__secondary:checked + label.overlay.overlay-secondary { | ||
&#pst-primary-sidebar-checkbox:checked + label.overlay.overlay-primary, | ||
&#pst-secondary-sidebar-checkbox:checked + label.overlay.overlay-secondary { | ||
Comment on lines
+31
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Appreciate the descriptive names here that clearly indicate component + function |
||
height: 100vh; | ||
width: 100vw; | ||
} | ||
|
||
// Primary sidebar slides in from the left | ||
&#__primary:checked ~ .bd-container .bd-sidebar-primary { | ||
&#pst-primary-sidebar-checkbox:checked ~ .bd-container .bd-sidebar-primary { | ||
visibility: visible; | ||
margin-left: 0; | ||
} | ||
|
||
// Secondary sidebar slides in from the right | ||
&#__secondary:checked ~ .bd-container .bd-sidebar-secondary { | ||
&#pst-secondary-sidebar-checkbox:checked | ||
~ .bd-container | ||
.bd-sidebar-secondary { | ||
visibility: visible; | ||
margin-right: 0; | ||
} | ||
|
@@ -82,11 +84,11 @@ input { | |
|
||
// Primary sidebar hides/shows at earlier widths | ||
@include media-breakpoint-up($breakpoint-sidebar-primary) { | ||
label.sidebar-toggle.primary-toggle { | ||
.sidebar-toggle.primary-toggle { | ||
display: none; | ||
} | ||
|
||
input#__primary { | ||
input#pst-primary-sidebar-checkbox { | ||
&:checked + label.overlay.overlay-primary { | ||
height: 0; | ||
width: 0; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
{% if theme_navbar_start or theme_navbar_center or theme_navbar_end or theme_navbar_persistent %} | ||
<div class="bd-header__inner bd-page-width"> | ||
<label class="sidebar-toggle primary-toggle" for="__primary"> | ||
<button class="sidebar-toggle primary-toggle" aria-label="{{ _('Site navigation') }}"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I chose this ARIA label and the one below because these strings are already in the translation system. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yay! we have real buttons now |
||
<span class="fa-solid fa-bars"></span> | ||
</label> | ||
</button> | ||
{% set navbar_start, navbar_class, navbar_align = navbar_align_class() %} | ||
{% if theme_navbar_start %} | ||
<div class="{{ navbar_start }} navbar-header-items__start"> | ||
|
@@ -40,9 +40,9 @@ | |
{% endfor %} | ||
|
||
{% if not remove_sidebar_secondary %} | ||
<label class="sidebar-toggle secondary-toggle" for="__secondary" tabindex="0"> | ||
<button class="sidebar-toggle secondary-toggle" aria-label="{{ _('On this page') }}"> | ||
<span class="fa-solid fa-outdent"></span> | ||
</label> | ||
</button> | ||
{% endif %} | ||
</div> | ||
{% endif %} |
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 chose to focus on the first tab stop in the sidebar after it opens because this behavior mimics the
<dialog>
element.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 makes absolute sense, this ensures focus remains visible