// Docusaurus renders code blocks as
unconditionally.
// IBM Equal Access (element_tabbable_role_valid) exempts tabbable elements
// only when they actually scroll — a non-scrollable with tabindex fails.
// Scrollable blocks get the scrollable-region pattern (role + unique name);
// non-scrollable blocks lose the needless tabindex.
function updateCodeBlocks() {
const pres = document.querySelectorAll("pre.prism-code");
let index = 0;
for (const pre of pres) {
index += 1;
const scrollable =
pre.scrollWidth > pre.clientWidth || pre.scrollHeight > pre.clientHeight;
if (scrollable) {
pre.setAttribute("tabindex", "0");
pre.setAttribute("role", "region");
pre.setAttribute("aria-label", `Code sample ${index}`);
} else {
pre.removeAttribute("tabindex");
pre.removeAttribute("role");
pre.removeAttribute("aria-label");
}
}
}
let observer;
let resizeTimer;
export function onRouteDidUpdate() {
if (typeof document === "undefined") {
return;
}
updateCodeBlocks();
if (!observer) {
observer = new MutationObserver(updateCodeBlocks);
// childList: re-rendered code blocks arrive without the attributes.
// attributeFilter "hidden": Docusaurus tabs toggle panels via the hidden
// attribute (no childList mutation) — a scrollable block inside an
// initially hidden tab measures scrollWidth 0 and loses its tabindex, so
// it must be re-evaluated when its tab becomes visible. The patched
// attributes (tabindex/role/aria-label) are not observed — no feedback loop.
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["hidden"],
});
// Scrollability is viewport-dependent — re-evaluate on resize.
window.addEventListener("resize", () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(updateCodeBlocks, 250);
});
}
}