Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f666630
Add scrollable-regions module to make overflowing code and output key…
cwickham Aug 26, 2026
38d87b5
Catch up editor tooling artifacts for cache-globals schema change
cwickham Aug 26, 2026
f1eb6f4
Regenerate esbuild analysis cache for scrollable-regions module
cwickham Aug 26, 2026
93eab23
Add localized labels for scrollable regions
cwickham Aug 26, 2026
053cd2b
Regenerate schema and editor tooling artifacts for scrollable-region …
cwickham Aug 26, 2026
ea74fd5
Match axe's scroll-container test when marking scrollable regions
cwickham Aug 26, 2026
939150b
Add tests for keyboard-focusable scrollable regions
cwickham Aug 26, 2026
2b4ccb1
Add changelog entry for #14378
cwickham Aug 26, 2026
2e3a12a
Don't mark visually-hidden code alternatives as scrollable regions
cwickham Aug 27, 2026
550c66f
Give numbered code blocks their own scrollable-region tab stop
cwickham Aug 27, 2026
d0799b8
Note why folded code re-syncs, so the mechanism isn't mistaken for a …
cwickham Aug 27, 2026
c9f3855
Group the scrollable-region labels with the other accessible names
cwickham Aug 27, 2026
f614c31
Merge branch 'main' into feat/14378-scrollable-regions
cwickham Aug 28, 2026
8b74783
Don't count disabled controls as a region's own tab stop
cwickham Sep 22, 2026
75a4ff0
Leave a region alone when the author set its own role
cwickham Sep 22, 2026
f01cec8
Re-sync a focused region once focus leaves it
cwickham Sep 22, 2026
aaac4b7
Merge remote-tracking branch 'origin/main' into feat/14378-scrollable…
cwickham Sep 22, 2026
ee1d7ad
Translate the scrollable-region labels into the built-in locales
cwickham Sep 22, 2026
0acad9a
Merge remote-tracking branch 'origin/feat/14378-scrollable-regions' i…
cwickham Sep 22, 2026
337a30c
Drop the local package/dist symlink from the branch
cwickham Sep 22, 2026
cee198c
Escape < when passing the scrollable-region labels to the page
cwickham Sep 22, 2026
a014c18
Sync scrollable regions after the last resize of a burst
cwickham Sep 22, 2026
e448509
Test that localized and overridden scrollable-region labels reach the…
cwickham Sep 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/changelog-1.11.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ All changes included in 1.11:
- ([#14376](https://github.com/quarto-dev/quarto-cli/issues/14376)): Add a distinct, localizable `aria-label` to each navigation landmark of websites and books: the navbar (`Site`), the sidebar (`Section`, or `Site` when there is no navbar), the narrow-viewport toolbar (`Toolbar`), the previous/next page navigation (`Page`), and the breadcrumbs (`Breadcrumbs`) (previously hardcoded English `breadcrumb`). The new `navigation-*-label` language keys can be overridden with `language:` metadata.
- ([#14376](https://github.com/quarto-dev/quarto-cli/issues/14376)): Translate the new `navigation-*-label` keys in all 33 built-in language files. The values are drawn from existing human-translated interface strings (LibreOffice, GNOME, Wikidata) and each one carries a comment naming its source; values that were adapted rather than used verbatim are marked `needs review`.
- ([#14376](https://github.com/quarto-dev/quarto-cli/issues/14376)): Label the table of contents `<nav>` with its localized title (`aria-labelledby`), in `html` and `revealjs` output, so assistive technology can tell it apart from other navigation landmarks.
- ([#14378](https://github.com/quarto-dev/quarto-cli/issues/14378)): Make scrollable code blocks and cell output keyboard-focusable in HTML output, so keyboard users can Tab to them and scroll with the arrow keys (axe rule `scrollable-region-focusable`, WCAG 2.1.1). While a region overflows it gets `tabindex="0"`, `role="group"`, and a localized `aria-label` (new `scrollable-code-label` and `scrollable-output-label` language keys, translated in all 34 built-in language files); when it fits again the attributes are removed. Chrome and Firefox already focus such regions natively; this adds Safari coverage and an accessible name.

## Formats

Expand Down
4 changes: 4 additions & 0 deletions src/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,8 @@ export const kToggleSidebar = "toggle-sidebar";
export const kToggleDarkMode = "toggle-dark-mode";
export const kToggleReaderMode = "toggle-reader-mode";
export const kToggleNavigation = "toggle-navigation";
export const kScrollableCodeLabel = "scrollable-code-label";
export const kScrollableOutputLabel = "scrollable-output-label";

export const kNavigationSiteLabel = "navigation-site-label";
export const kNavigationSectionLabel = "navigation-section-label";
Expand Down Expand Up @@ -445,6 +447,8 @@ export const kLanguageDefaultsKeys = [
kNavigationToolbarLabel,
kNavigationPageLabel,
kNavigationBreadcrumbsLabel,
kScrollableCodeLabel,
kScrollableOutputLabel,
kCopyButtonTooltip,
kCopyButtonTooltipSuccess,
kBackToTop,
Expand Down
4 changes: 4 additions & 0 deletions src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ import {
kRepoActionLinksIssue,
kRepoActionLinksSource,
kResourcePath,
kScrollableCodeLabel,
kScrollableOutputLabel,
kSearchClearButtonTitle,
kSearchCopyLinkTitle,
kSearchDetatchedCancelButtonTitle,
Expand Down Expand Up @@ -713,6 +715,8 @@ export interface FormatLanguage {
[kNavigationToolbarLabel]?: string;
[kNavigationPageLabel]?: string;
[kNavigationBreadcrumbsLabel]?: string;
[kScrollableCodeLabel]?: string;
[kScrollableOutputLabel]?: string;
[kSearchMatchingDocumentsText]?: string;
[kSearchCopyLinkTitle]?: string;
[kSearchMoreMatchText]?: string;
Expand Down
5 changes: 5 additions & 0 deletions src/format/html/format-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,11 @@ export async function htmlFormatExtras(
// Add localization strings
options.language = format.language;

// the scrollable-regions module in quarto.js reads its localized labels
// from a global set by the after-body script; quarto.js only ships with
// bootstrap formats
options.scrollableRegions = bootstrap;

// anchors if required
if (options.anchors) {
scripts.push({
Expand Down
8 changes: 5 additions & 3 deletions src/resources/editor/tools/vs-code.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10403,6 +10403,8 @@ var require_yaml_intelligence_resources = __commonJS({
"copy-button-tooltip": "string",
"copy-button-tooltip-success": "string",
"skip-to-content": "string",
"scrollable-code-label": "string",
"scrollable-output-label": "string",
"repo-action-links-edit": "string",
"repo-action-links-source": "string",
"repo-action-links-issue": "string",
Expand Down Expand Up @@ -25589,12 +25591,12 @@ var require_yaml_intelligence_resources = __commonJS({
mermaid: "%%"
},
"handlers/mermaid/schema.yml": {
_internalId: 218300,
_internalId: 218304,
type: "object",
description: "be an object",
properties: {
"mermaid-format": {
_internalId: 218292,
_internalId: 218296,
type: "enum",
enum: [
"png",
Expand All @@ -25610,7 +25612,7 @@ var require_yaml_intelligence_resources = __commonJS({
exhaustiveCompletions: true
},
theme: {
_internalId: 218299,
_internalId: 218303,
type: "anyOf",
anyOf: [
{
Expand Down

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions src/resources/editor/tools/yaml/web-worker.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -3371,6 +3371,8 @@
"copy-button-tooltip": "string",
"copy-button-tooltip-success": "string",
"skip-to-content": "string",
"scrollable-code-label": "string",
"scrollable-output-label": "string",
"repo-action-links-edit": "string",
"repo-action-links-source": "string",
"repo-action-links-issue": "string",
Expand Down Expand Up @@ -18557,12 +18559,12 @@
"mermaid": "%%"
},
"handlers/mermaid/schema.yml": {
"_internalId": 218300,
"_internalId": 218304,
"type": "object",
"description": "be an object",
"properties": {
"mermaid-format": {
"_internalId": 218292,
"_internalId": 218296,
"type": "enum",
"enum": [
"png",
Expand All @@ -18578,7 +18580,7 @@
"exhaustiveCompletions": true
},
"theme": {
"_internalId": 218299,
"_internalId": 218303,
"type": "anyOf",
"anyOf": [
{
Expand Down
11 changes: 8 additions & 3 deletions src/resources/formats/html/esbuild-analysis-cache.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"quarto.js": {
"inputs": {
"quarto.js": {
"bytes": 26830,
"bytes": 27594,
"imports": [],
"format": "esm"
}
Expand All @@ -14,16 +14,21 @@
"path": "./tabsets/tabsets.js",
"kind": "import-statement",
"external": true
},
{
"path": "./scrollable-regions/scrollable-regions.js",
"kind": "import-statement",
"external": true
}
],
"exports": [],
"entryPoint": "quarto.js",
"inputs": {
"quarto.js": {
"bytesInOutput": 22313
"bytesInOutput": 22490
}
},
"bytes": 22313
"bytes": 22490
}
}
}
Expand Down
38 changes: 29 additions & 9 deletions src/resources/formats/html/quarto.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as tabsets from "./tabsets/tabsets.js";
import * as scrollableRegions from "./scrollable-regions/scrollable-regions.js";

const sectionChanged = new CustomEvent("quarto-sectionChanged", {
detail: {},
Expand Down Expand Up @@ -34,21 +35,25 @@ const layoutMarginEls = () => {
};

window.document.addEventListener("DOMContentLoaded", function (_event) {
// Recompute the position of margin elements anytime the body size changes
// Recompute the position of margin elements and re-sync scrollable regions
// anytime the body size changes. Trailing, so a resize that ends inside the
// wait is still handled rather than left at a size the page no longer has.
if (window.ResizeObserver) {
const resizeObserver = new window.ResizeObserver(
throttle(() => {
layoutMarginEls();
scrollableRegions.syncScrollableRegions();
if (
window.document.body.getBoundingClientRect().width < 990 &&
isReaderMode()
) {
quartoToggleReader();
}
}, 50)
}, 50, true)
);
resizeObserver.observe(window.document.body);
}
scrollableRegions.syncScrollableRegions();

const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]');
const sidebarEl = window.document.getElementById("quarto-sidebar");
Expand Down Expand Up @@ -827,16 +832,31 @@ window.document.addEventListener("DOMContentLoaded", function (_event) {

tabsets.init();

function throttle(func, wait) {
// With `trailing`, a call dropped during the wait runs once the wait ends, so
// the last event of a burst is never lost.
function throttle(func, wait, trailing = false) {
let waiting = false;
let pending = null;
const release = function () {
if (pending) {
const [context, args] = pending;
pending = null;
func.apply(context, args);
setTimeout(release, wait);
} else {
waiting = false;
}
};
return function () {
if (!waiting) {
func.apply(this, arguments);
waiting = true;
setTimeout(function () {
waiting = false;
}, wait);
if (waiting) {
if (trailing) {
pending = [this, arguments];
}
return;
}
func.apply(this, arguments);
waiting = true;
setTimeout(release, wait);
};
}

Expand Down
Loading
Loading