Improve announcements after navigation (#1015)

This commit is contained in:
Sascha Ißbrücker
2025-03-16 12:24:25 +01:00
committed by GitHub
parent 226eb69f8b
commit e45dffb9cb
4 changed files with 150 additions and 9 deletions

View File

@@ -57,3 +57,68 @@ export class FocusTrapController {
}
}
}
let afterPageLoadFocusTarget = [];
let firstPageLoad = true;
export function setAfterPageLoadFocusTarget(...targets) {
afterPageLoadFocusTarget = targets;
}
function programmaticFocus(element) {
// Ensure element is focusable
// Hide focus outline if element is not focusable by default - might
// reconsider this later
const isFocusable = element.tabIndex >= 0;
if (!isFocusable) {
// Apparently the default tabIndex is -1, even though an element is still
// not focusable with that. Setting an explicit -1 also sets the attribute
// and the element becomes focusable.
element.tabIndex = -1;
// `focusVisible` is not supported in all browsers, so hide the outline manually
element.style["outline"] = "none";
}
element.focus({
focusVisible: isKeyboardActive() && isFocusable,
preventScroll: true,
});
}
// Register global listener for navigation and try to focus an element that
// results in a meaningful announcement.
document.addEventListener("turbo:load", () => {
// Ignore initial page load to let the browser handle announcements
if (firstPageLoad) {
firstPageLoad = false;
return;
}
// Check if there is an explicit focus target for the next page load
for (const target of afterPageLoadFocusTarget) {
const element = document.querySelector(target);
if (element) {
programmaticFocus(element);
return;
}
}
afterPageLoadFocusTarget = [];
// If there is some autofocus element, let the browser handle it
const autofocus = document.querySelector("[autofocus]");
if (autofocus) {
return;
}
// If there is a toast as a result of some action, focus it
const toast = document.querySelector(".toast");
if (toast) {
programmaticFocus(toast);
return;
}
// Otherwise go with main
const main = document.querySelector("main");
if (main) {
programmaticFocus(main);
}
});