Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -24,6 +24,7 @@ All changes included in 1.11:
- ([#14952](https://github.com/quarto-dev/quarto-cli/issues/14952)): Fix `revealjs` presentations disabling pinch-zoom on touch devices. The viewport `<meta>` tag no longer sets `maximum-scale=1.0` and `user-scalable=no`, which axe-core flagged (`meta-viewport`).
- ([#15011](https://github.com/quarto-dev/quarto-cli/issues/15011)): Fix the slide menu of `revealjs` presentations being read by screen readers while it is closed and off screen, and Tab moving keyboard focus to it once it lists more slides than fit. (author: @jooyoungseo)
- ([#15015](https://github.com/quarto-dev/quarto-cli/issues/15015)): Fix the "Back to Article" link of notebook previews in website projects not being reachable or usable with the keyboard.
- ([#15019](https://github.com/quarto-dev/quarto-cli/issues/15019)): Fix screen reader announcements in `revealjs` presentations including incremental content before it is revealed and repeating slides and fragments when browser history is enabled. (author: @jooyoungseo)
- ([#15030](https://github.com/quarto-dev/quarto-cli/pull/15030)): Fix `axe` checks in `revealjs` presentations hiding the current slide from screen readers when moving to another slide while the scan runs.

## Formats
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
diff --git a/js/controllers/fragments.js b/js/controllers/fragments.js
index 0a84530..361d5c3 100644
--- a/js/controllers/fragments.js
+++ b/js/controllers/fragments.js
@@ -188,6 +188,7 @@ export default class Fragments {
if( fragments.length ) {

let maxIndex = 0;
+ let fragmentsToAnnounce = [];

if( typeof index !== 'number' ) {
let currentFragment = this.sort( slide.querySelectorAll( '.fragment.visible' ) ).pop();
@@ -211,8 +212,7 @@ export default class Fragments {
el.classList.remove( 'current-fragment' );

if( i === index ) {
- // Announce the fragments one by one to the Screen Reader
- this.Reveal.announceStatus( this.Reveal.getStatusText( el ) );
+ fragmentsToAnnounce.push( el );

el.classList.add( 'current-fragment' );
this.Reveal.slideContent.startEmbeddedContent( el );
@@ -246,6 +246,12 @@ export default class Fragments {

} );

+ // Read and announce each current fragment only after all fragment
+ // classes are updated, so nested fragments have their final state.
+ fragmentsToAnnounce.forEach( el => {
+ this.Reveal.announceStatus( this.Reveal.getStatusText( el ) );
+ } );
+
// Write the current fragment index to the slide <section>.
// This can be used by end users to apply styles based on
// the current fragment index.
diff --git a/js/controllers/location.js b/js/controllers/location.js
index 2299d47..b0dbc01 100644
--- a/js/controllers/location.js
+++ b/js/controllers/location.js
@@ -105,8 +105,14 @@ export default class Location {
const newIndices = this.getIndicesFromHash();

if( newIndices ) {
- if( ( newIndices.h !== currentIndices.h || newIndices.v !== currentIndices.v || newIndices.f !== undefined ) ) {
- this.Reveal.slide( newIndices.h, newIndices.v, newIndices.f );
+ const fragmentInURL = this.Reveal.getConfig().fragmentInURL;
+ const newFragmentIndex = newIndices.f === undefined && fragmentInURL ? -1 : newIndices.f;
+ const fragmentChanged = newFragmentIndex !== undefined && newFragmentIndex !== ( currentIndices.f ?? -1 );
+
+ if( newIndices.h !== currentIndices.h ||
+ ( newIndices.v ?? 0 ) !== ( currentIndices.v ?? 0 ) ||
+ fragmentChanged ) {
+ this.Reveal.slide( newIndices.h, newIndices.v, newFragmentIndex );
}
}
// If no new indices are available, we're trying to navigate to
diff --git a/js/reveal.js b/js/reveal.js
index 927b0a1..52b19df 100644
--- a/js/reveal.js
+++ b/js/reveal.js
@@ -381,7 +381,7 @@ export default function( revealElement, options ) {
* that can be announced to a screen reader. Hidden
* elements are excluded.
*/
- function getStatusText( node ) {
+ function getStatusText( node, rootNode = node ) {

let text = '';

@@ -394,10 +394,33 @@ export default function( revealElement, options ) {

let isAriaHidden = node.getAttribute( 'aria-hidden' );
let isDisplayHidden = window.getComputedStyle( node )['display'] === 'none';
- if( isAriaHidden !== 'true' && !isDisplayHidden ) {
+ let isFragmentHidden = false;
+
+ // Fragment visibility is class-based since computed styles may still
+ // reflect an in-progress transition when an announcement is made.
+ // The root is exempt so a fragment can announce itself as it changes.
+ if( node !== rootNode && node.classList.contains( 'fragment' ) && !node.classList.contains( 'custom' ) ) {
+ const isVisible = node.classList.contains( 'visible' );
+ const isCurrent = node.classList.contains( 'current-fragment' );
+ const isCurrentOnly = node.classList.contains( 'current-visible' ) || node.classList.contains( 'fade-in-then-out' );
+ const isInitiallyVisible = ['grow', 'shrink', 'strike', 'semi-fade-out'].some( style => node.classList.contains( style ) ) ||
+ Array.from( node.classList ).some( style => /^highlight-(current-)?(red|green|blue)$/.test( style ) );
+
+ if( isCurrentOnly ) {
+ isFragmentHidden = !isCurrent;
+ }
+ else if( node.classList.contains( 'fade-out' ) ) {
+ isFragmentHidden = isVisible;
+ }
+ else {
+ isFragmentHidden = !isVisible && !isInitiallyVisible;
+ }
+ }
+
+ if( isAriaHidden !== 'true' && !isDisplayHidden && !isFragmentHidden ) {

Array.from( node.childNodes ).forEach( child => {
- text += getStatusText( child );
+ text += getStatusText( child, rootNode );
} );

}
19 changes: 18 additions & 1 deletion package/src/common/update-html-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,24 @@ export async function updateHtmlDependencies(config: Configuration) {
"hakimel/reveal.js",
"REVEAL_JS",
workingDir,
(dir: string, version: string) => {
async (dir: string, version: string) => {
// Backport the unmerged PR hakimel/reveal.js#3946 for reveal.js 5.1.0.
// Before removing the source patch and rebuild step, check that the PR
// has landed in the reveal.js release being bundled.
const sourceDir = join(dir, `reveal.js-${version}`);
await runCmd("git", [
"-C", sourceDir, "apply", "--ignore-space-change",
join(patchesDir, "revealjs-0001-screen-reader-announcements.patch"),
]);
// Skip install scripts: rebuilding the bundles does not need Puppeteer.
await runCmd(config.os === "windows" ? "npm.cmd" : "npm", [
"--prefix", sourceDir, "ci", "--ignore-scripts",
]);
await runCmd("node", [
join(sourceDir, "node_modules", "gulp", "bin", "gulp.js"),
"--cwd", sourceDir, "js",
]);

// Copy the desired resource files
info("Copying reveal.js resources' directory");
if (existsSync(revealJs)) {
Expand Down
2 changes: 1 addition & 1 deletion src/resources/formats/revealjs/reveal/dist/reveal.esm.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/resources/formats/revealjs/reveal/dist/reveal.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/resources/formats/revealjs/reveal/dist/reveal.js.map

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions tests/docs/playwright/revealjs/announcements-fragment-in-url.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
pagetitle: Reveal.js announcements with fragment URLs
format:
revealjs:
history: true
fragment-in-url: true
---

## Intro {#intro}

## Steps {#steps}

Visible lead

<div class="fragment grow">Grow starts visible</div>

<div class="fragment fade-out">Fade out starts visible</div>

<div class="fragment">Outer step <span class="fragment">Nested step</span></div>

Before pause

. . .

After pause

## History target {#history-target}

## Nested fade out {#nested-fade-out}

<div class="fragment">Parent one <span class="fragment fade-out">Child fades</span></div>

## Nested appear {#nested-appear}

<div class="fragment">Parent two <span class="fragment">Child appears</span></div>
34 changes: 34 additions & 0 deletions tests/docs/playwright/revealjs/announcements.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
pagetitle: Reveal.js announcements
format:
revealjs:
history: true
---

## Intro {#intro}

## Steps {#steps}

Visible lead

<div class="fragment grow">Grow starts visible</div>

<div class="fragment fade-out">Fade out starts visible</div>

<div class="fragment">Outer step <span class="fragment">Nested step</span></div>

Before pause

. . .

After pause

## History target {#history-target}

## Nested fade out {#nested-fade-out}

<div class="fragment">Parent one <span class="fragment fade-out">Child fades</span></div>

## Nested appear {#nested-appear}

<div class="fragment">Parent two <span class="fragment">Child appears</span></div>
Loading
Loading