Add "skip to content", "skip to navigation" links (#38006)

This commit is contained in:
diondiondion authored and GitHub committed 2026-03-02 14:37:33 +00:00
1 parent 87004ddb96
commit 816e63d2a5
10 files changed
+281 -43

No files matched your search

@@ -1,3 +1,8 @@
import {
getColumnSkipLinkId,
getNavigationSkipLinkId,
} from '../components/skip_links';
/**
* Out of a list of elements, return the first one whose top edge
* is inside of the viewport, and return the element and its BoundingClientRect.
@@ -20,9 +25,29 @@ function findFirstVisibleWithRect(
return null;
}
function focusColumnTitle(index: number, multiColumn: boolean) {
if (multiColumn) {
const column = document.querySelector(`.column:nth-child(${index})`);
if (column) {
column
.querySelector<HTMLAnchorElement>(
`#${getColumnSkipLinkId(index - 1)}, #${getNavigationSkipLinkId()}`,
)
?.focus();
}
} else {
const idSelector =
index === 2
? `#${getNavigationSkipLinkId()}`
: `#${getColumnSkipLinkId(1)}`;
document.querySelector<HTMLAnchorElement>(idSelector)?.focus();
}
}
/**
* Move focus to the column of the passed index (1-based).
* Focus is placed on the topmost visible item
* Focus is placed on the topmost visible item, or the column title
*/
export function focusColumn(index = 1) {
// Skip the leftmost drawer in multi-column mode
@@ -35,11 +60,21 @@ export function focusColumn(index = 1) {
`.column:nth-child(${index + indexOffset})`,
);
if (!column) return;
function fallback() {
focusColumnTitle(index + indexOffset, isMultiColumnLayout);
}
if (!column) {
fallback();
return;
}
const container = column.querySelector('.scrollable');
if (!container) return;
if (!container) {
fallback();
return;
}
const focusableItems = Array.from(
container.querySelectorAll<HTMLElement>(
@@ -50,20 +85,23 @@ export function focusColumn(index = 1) {
// Find first item visible in the viewport
const itemToFocus = findFirstVisibleWithRect(focusableItems);
if (itemToFocus) {
const viewportWidth =
window.innerWidth || document.documentElement.clientWidth;
const { item, rect } = itemToFocus;
if (
container.scrollTop > item.offsetTop ||
rect.right > viewportWidth ||
rect.left < 0
) {
itemToFocus.item.scrollIntoView(true);
}
itemToFocus.item.focus();
if (!itemToFocus) {
fallback();
return;
}
const viewportWidth =
window.innerWidth || document.documentElement.clientWidth;
const { item, rect } = itemToFocus;
if (
container.scrollTop > item.offsetTop ||
rect.right > viewportWidth ||
rect.left < 0
) {
itemToFocus.item.scrollIntoView(true);
}
itemToFocus.item.focus();
}
/**