Skip to content

Commit 303b95f

Browse files
jooyoungseocderv
andauthored
revealjs: improve speaker view labels and landmark navigation (#15164)
The speaker view's Layout select had no accessible name, and its current slide, upcoming slide, time, and notes regions had no landmarks. The preview iframes were unnamed, so screen reader users could not tell these parts apart. Keys pressed on native controls were also forwarded to the slide preview. reveal.js 5.1.0 lacks these fixes, so this backports hakimel/reveal.js#3953 and rebuilds the bundles from the 5.1.0 source. - Give the Layout select a native accessible name and a visible focus indicator - Add named region landmarks for Current slide, Upcoming slide, Time, and Speaker notes, keeping the hidden-layout and empty-notes behavior - Name the preview iframes and stop forwarding keys from native controls to the preview - Replace the slide-controls aside with a div so it adds no complementary landmark - Rebuild the core and plugin bundles from source, and have the dependency-update script rebuild plugins too Fixes #15159 Fixes #15160 --------- Co-authored-by: Christophe Dervieux <christophe.dervieux@gmail.com>
1 parent 085abf7 commit 303b95f

12 files changed

Lines changed: 260 additions & 19 deletions

File tree

‎news/changelog-1.11.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ All changes included in 1.11:
4343
- ([#15079](https://github.com/quarto-dev/quarto-cli/issues/15079)): Fix keyboard focus in `revealjs` presentations moving to the page when coming back to the presentation's browser tab, which took readers off the link or chart they were on and made screen readers stop passing the arrow keys to the presentation. (author: @jooyoungseo)
4444
- ([#15080](https://github.com/quarto-dev/quarto-cli/issues/15080)): Fix footnote, citation, cross-reference and code annotation popups not closing with Esc, and staying open over the next focused element after keyboard focus leaves them.
4545
- ([#15129](https://github.com/quarto-dev/quarto-cli/issues/15129)): Fix default listings rendering an empty, focusable metadata link for posts with no date, author or other listed field to show, flagged by axe-core (`link-name`).
46+
- ([#15159](https://github.com/quarto-dev/quarto-cli/issues/15159), [#15160](https://github.com/quarto-dev/quarto-cli/issues/15160)): Fix the `revealjs` speaker view's Layout selector having no accessible name or visible keyboard focus, and its current slide, upcoming slide, time, and notes areas not being named regions. Changing layouts no longer forwards control keys to the slides, and the slide navigation buttons no longer introduce an unrelated complementary landmark. (author: @jooyoungseo)
4647

4748
## Formats
4849

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
diff --git a/js/controllers/controls.js b/js/controllers/controls.js
2+
index 3d150da..5913bbf 100644
3+
--- a/js/controllers/controls.js
4+
+++ b/js/controllers/controls.js
5+
@@ -35,7 +35,7 @@ export default class Controls {
6+
const rtl = this.Reveal.getConfig().rtl;
7+
const revealElement = this.Reveal.getRevealElement();
8+
9+
- this.element = document.createElement( 'aside' );
10+
+ this.element = document.createElement( 'div' );
11+
this.element.className = 'controls';
12+
this.element.innerHTML =
13+
`<button class="navigate-left" aria-label="${ rtl ? 'next slide' : 'previous slide' }"><div class="controls-arrow"></div></button>
14+
@@ -276,4 +276,4 @@ export default class Controls {
15+
16+
}
17+
18+
-}
19+
\ No newline at end of file
20+
+}
21+
diff --git a/plugin/notes/speaker-view.html b/plugin/notes/speaker-view.html
22+
index 55bc01f..5653edc 100644
23+
--- a/plugin/notes/speaker-view.html
24+
+++ b/plugin/notes/speaker-view.html
25+
@@ -206,6 +206,11 @@
26+
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
27+
}
28+
29+
+ #speaker-layout:focus-within {
30+
+ outline: 2px solid currentColor;
31+
+ outline-offset: 3px;
32+
+ }
33+
+
34+
#speaker-layout select:focus {
35+
outline: none;
36+
box-shadow: none;
37+
@@ -308,10 +313,10 @@
38+
39+
<div id="connection-status">Loading speaker view...</div>
40+
41+
- <div id="current-slide"></div>
42+
- <div id="upcoming-slide"><span class="overlay-element label">Upcoming</span></div>
43+
+ <div id="current-slide" role="region" aria-label="Current slide"></div>
44+
+ <div id="upcoming-slide" role="region" aria-label="Upcoming slide"><span class="overlay-element label">Upcoming</span></div>
45+
<div id="speaker-controls">
46+
- <div class="speaker-controls-time">
47+
+ <div class="speaker-controls-time" role="region" aria-label="Time">
48+
<h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>
49+
<div class="clock">
50+
<span class="clock-value">0:00 AM</span>
51+
@@ -327,14 +332,14 @@
52+
</div>
53+
</div>
54+
55+
- <div class="speaker-controls-notes hidden">
56+
+ <div class="speaker-controls-notes hidden" role="region" aria-label="Speaker notes">
57+
<h4 class="label">Notes</h4>
58+
<div class="value"></div>
59+
</div>
60+
</div>
61+
<div id="speaker-layout" class="overlay-element interactive">
62+
- <span class="speaker-layout-label"></span>
63+
- <select class="speaker-layout-dropdown"></select>
64+
+ <label for="speaker-layout-dropdown" class="speaker-layout-label">Layout</label><span class="speaker-layout-value" aria-hidden="true"></span>
65+
+ <select id="speaker-layout-dropdown" class="speaker-layout-dropdown"></select>
66+
</div>
67+
68+
<script>
69+
@@ -519,6 +524,9 @@
70+
event.preventDefault();
71+
return false;
72+
}
73+
+ // Let native controls handle their own keys without advancing slides.
74+
+ if( event.target.closest( 'input, select, textarea, button, [contenteditable]' ) ) return;
75+
+
76+
currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );
77+
} );
78+
79+
@@ -544,12 +552,14 @@
80+
var upcomingURL = data.url + urlSeparator + params + '&scrollActivationWidth=false&controls=false' + hash;
81+
82+
currentSlide = document.createElement( 'iframe' );
83+
+ currentSlide.setAttribute( 'title', 'Current slide preview' );
84+
currentSlide.setAttribute( 'width', 1280 );
85+
currentSlide.setAttribute( 'height', 1024 );
86+
currentSlide.setAttribute( 'src', currentURL );
87+
document.querySelector( '#current-slide' ).appendChild( currentSlide );
88+
89+
upcomingSlide = document.createElement( 'iframe' );
90+
+ upcomingSlide.setAttribute( 'title', 'Upcoming slide preview' );
91+
upcomingSlide.setAttribute( 'width', 640 );
92+
upcomingSlide.setAttribute( 'height', 512 );
93+
upcomingSlide.setAttribute( 'src', upcomingURL );
94+
@@ -778,7 +788,7 @@
95+
function setupLayout() {
96+
97+
layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
98+
- layoutLabel = document.querySelector( '.speaker-layout-label' );
99+
+ layoutLabel = document.querySelector( '.speaker-layout-value' );
100+
101+
// Render the list of available layouts
102+
for( var id in SPEAKER_LAYOUTS ) {
103+
@@ -808,7 +818,7 @@
104+
105+
var title = SPEAKER_LAYOUTS[ value ];
106+
107+
- layoutLabel.innerHTML = 'Layout' + ( title ? ( ': ' + title ) : '' );
108+
+ layoutLabel.textContent = title ? ( ': ' + title ) : '';
109+
layoutDropdown.value = value;
110+
111+
document.body.setAttribute( 'data-speaker-layout', value );
112+
@@ -895,4 +905,4 @@
113+
114+
</script>
115+
</body>
116+
-</html>
117+
\ No newline at end of file
118+
+</html>

‎package/src/common/update-html-dependencies.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -308,14 +308,18 @@ export async function updateHtmlDependencies(config: Configuration) {
308308
// Backport the unmerged PRs hakimel/reveal.js#3946 and #3949, plus
309309
// native image alt text from reveal.js 6, to 5.1.0, and keep the focus
310310
// where it was when the page is shown again (#15079), which no reveal.js
311-
// release does yet. Before removing these source patches and rebuild
312-
// step, verify all fixes are in the release we bundle. Each patch
313-
// applies after the one before it.
311+
// release does yet. Also backport the speaker-view accessible label,
312+
// named regions, and neutral slide-controls container (#15159, #15160;
313+
// upstream hakimel/reveal.js#3953).
314+
// Before removing these source patches and rebuild step, verify all
315+
// fixes are in the release we bundle. Each patch applies after the
316+
// one before it.
314317
const sourceDir = join(dir, `reveal.js-${version}`);
315318
for (const patch of [
316319
"revealjs-0001-screen-reader-announcements.patch",
317320
"revealjs-0002-image-announcements.patch",
318321
"revealjs-0003-focus-on-return.patch",
322+
"revealjs-0004-speaker-view-accessibility.patch",
319323
]) {
320324
await runCmd("git", [
321325
"-C", sourceDir, "apply", "--ignore-space-change",
@@ -328,7 +332,7 @@ export async function updateHtmlDependencies(config: Configuration) {
328332
]);
329333
await runCmd("node", [
330334
join(sourceDir, "node_modules", "gulp", "bin", "gulp.js"),
331-
"--cwd", sourceDir, "js",
335+
"--cwd", sourceDir, "js", "plugins",
332336
]);
333337

334338
// Copy the desired resource files

‎src/resources/formats/revealjs/reveal/dist/reveal.esm.js‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/resources/formats/revealjs/reveal/dist/reveal.esm.js.map‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/resources/formats/revealjs/reveal/dist/reveal.js‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/resources/formats/revealjs/reveal/dist/reveal.js.map‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/resources/formats/revealjs/reveal/plugin/notes/notes.esm.js‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/resources/formats/revealjs/reveal/plugin/notes/notes.js‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/resources/formats/revealjs/reveal/plugin/notes/speaker-view.html‎

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@
206206
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
207207
}
208208

209+
#speaker-layout:focus-within {
210+
outline: 2px solid currentColor;
211+
outline-offset: 3px;
212+
}
213+
209214
#speaker-layout select:focus {
210215
outline: none;
211216
box-shadow: none;
@@ -308,10 +313,10 @@
308313

309314
<div id="connection-status">Loading speaker view...</div>
310315

311-
<div id="current-slide"></div>
312-
<div id="upcoming-slide"><span class="overlay-element label">Upcoming</span></div>
316+
<div id="current-slide" role="region" aria-label="Current slide"></div>
317+
<div id="upcoming-slide" role="region" aria-label="Upcoming slide"><span class="overlay-element label">Upcoming</span></div>
313318
<div id="speaker-controls">
314-
<div class="speaker-controls-time">
319+
<div class="speaker-controls-time" role="region" aria-label="Time">
315320
<h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>
316321
<div class="clock">
317322
<span class="clock-value">0:00 AM</span>
@@ -327,14 +332,14 @@ <h4 class="label pacing-title" style="display: none">Pacing – Time to finish c
327332
</div>
328333
</div>
329334

330-
<div class="speaker-controls-notes hidden">
335+
<div class="speaker-controls-notes hidden" role="region" aria-label="Speaker notes">
331336
<h4 class="label">Notes</h4>
332337
<div class="value"></div>
333338
</div>
334339
</div>
335340
<div id="speaker-layout" class="overlay-element interactive">
336-
<span class="speaker-layout-label"></span>
337-
<select class="speaker-layout-dropdown"></select>
341+
<label for="speaker-layout-dropdown" class="speaker-layout-label">Layout</label><span class="speaker-layout-value" aria-hidden="true"></span>
342+
<select id="speaker-layout-dropdown" class="speaker-layout-dropdown"></select>
338343
</div>
339344

340345
<script>
@@ -519,6 +524,9 @@ <h4 class="label">Notes</h4>
519524
event.preventDefault();
520525
return false;
521526
}
527+
// Let native controls handle their own keys without advancing slides.
528+
if( event.target.closest( 'input, select, textarea, button, [contenteditable]' ) ) return;
529+
522530
currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );
523531
} );
524532

@@ -544,12 +552,14 @@ <h4 class="label">Notes</h4>
544552
var upcomingURL = data.url + urlSeparator + params + '&scrollActivationWidth=false&controls=false' + hash;
545553

546554
currentSlide = document.createElement( 'iframe' );
555+
currentSlide.setAttribute( 'title', 'Current slide preview' );
547556
currentSlide.setAttribute( 'width', 1280 );
548557
currentSlide.setAttribute( 'height', 1024 );
549558
currentSlide.setAttribute( 'src', currentURL );
550559
document.querySelector( '#current-slide' ).appendChild( currentSlide );
551560

552561
upcomingSlide = document.createElement( 'iframe' );
562+
upcomingSlide.setAttribute( 'title', 'Upcoming slide preview' );
553563
upcomingSlide.setAttribute( 'width', 640 );
554564
upcomingSlide.setAttribute( 'height', 512 );
555565
upcomingSlide.setAttribute( 'src', upcomingURL );
@@ -778,7 +788,7 @@ <h4 class="label">Notes</h4>
778788
function setupLayout() {
779789

780790
layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
781-
layoutLabel = document.querySelector( '.speaker-layout-label' );
791+
layoutLabel = document.querySelector( '.speaker-layout-value' );
782792

783793
// Render the list of available layouts
784794
for( var id in SPEAKER_LAYOUTS ) {
@@ -808,7 +818,7 @@ <h4 class="label">Notes</h4>
808818

809819
var title = SPEAKER_LAYOUTS[ value ];
810820

811-
layoutLabel.innerHTML = 'Layout' + ( title ? ( ': ' + title ) : '' );
821+
layoutLabel.textContent = title ? ( ': ' + title ) : '';
812822
layoutDropdown.value = value;
813823

814824
document.body.setAttribute( 'data-speaker-layout', value );
@@ -895,4 +905,4 @@ <h4 class="label">Notes</h4>
895905

896906
</script>
897907
</body>
898-
</html>
908+
</html>

0 commit comments

Comments
 (0)