Skip to content
Open
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 @@ -33,6 +33,7 @@ All changes included in 1.11:
- ([#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.
- ([#15033](https://github.com/quarto-dev/quarto-cli/issues/15033)): Fix screen reader announcements in `revealjs` presentations reading the CSS inside Mermaid diagrams, omitting image alternative text, and reading the contents of `role="img"` graphics instead of their accessible names. (author: @jooyoungseo)
- ([#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)

## Formats

Expand Down
33 changes: 33 additions & 0 deletions package/src/common/patches/revealjs-0003-focus-on-return.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
diff --git a/js/reveal.js b/js/reveal.js
index 8e5f5ee..82f66f6 100644
--- a/js/reveal.js
+++ b/js/reveal.js
@@ -2841,14 +2841,20 @@ export default function( revealElement, options ) {
*/
function onPageVisibilityChange( event ) {

- // If, after clicking a link or similar and we're coming back,
- // focus the document.body to ensure we can use keyboard shortcuts
- if( document.hidden === false && document.activeElement !== document.body ) {
- // Not all elements support .blur() - SVGs among them.
- if( typeof document.activeElement.blur === 'function' ) {
- document.activeElement.blur();
- }
- document.body.focus();
+ // If, after clicking into a frame such as an embedded video, we're
+ // coming back, move the focus to the presentation so that keyboard
+ // shortcuts work: keys pressed in a frame never reach reveal.js.
+ // Anywhere else they do, so leave the focus where the reader left it.
+ // On the body it would take them out of what they were on, and screen
+ // readers in browse mode do not pass the arrow keys on from there.
+ if( document.hidden === false && document.activeElement && document.activeElement.tagName === 'IFRAME' ) {
+ // Blur the frame first, as reveal.js did: in Playwright's Firefox,
+ // focusing the deck straight from a frame leaves the focus there.
+ document.activeElement.blur();
+ if( !dom.wrapper.hasAttribute( 'tabindex' ) ) {
+ dom.wrapper.setAttribute( 'tabindex', '-1' );
+ }
+ dom.wrapper.focus( { preventScroll: true } );
}

}
9 changes: 6 additions & 3 deletions package/src/common/update-html-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,16 @@ export async function updateHtmlDependencies(config: Configuration) {
workingDir,
async (dir: string, version: string) => {
// Backport the unmerged PRs hakimel/reveal.js#3946 and #3949, plus
// native image alt text from reveal.js 6, to 5.1.0. Before removing these
// source patches and rebuild step, verify all fixes are in the release
// we bundle. The image patch applies after the screen reader patch.
// native image alt text from reveal.js 6, to 5.1.0, and keep the focus
// where it was when the page is shown again (#15079), which no reveal.js
// release does yet. Before removing these source patches and rebuild
// step, verify all fixes are in the release we bundle. Each patch
// applies after the one before it.
const sourceDir = join(dir, `reveal.js-${version}`);
for (const patch of [
"revealjs-0001-screen-reader-announcements.patch",
"revealjs-0002-image-announcements.patch",
"revealjs-0003-focus-on-return.patch",
]) {
await runCmd("git", [
"-C", sourceDir, "apply", "--ignore-space-change",
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.

14 changes: 14 additions & 0 deletions tests/docs/playwright/revealjs/visibility-focus.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
pagetitle: Focus when the presentation is shown again
format: revealjs
---

## Slide 1 {#slide-1}

[A link on slide-1](https://example.com/)

<iframe srcdoc="<button type='button'>Button in a frame</button>" title="A frame" height="80"></iframe>

## Slide 2 {#slide-2}

Text only.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { expect, Page, test } from '@playwright/test';

// Playwright does not hide the page when another tab is brought to the front,
// so mark it hidden and then visible again, as switching tabs does.
const hideAndShow = (page: Page) =>
page.evaluate(() => {
const set = (hidden: boolean) => {
Object.defineProperty(document, 'hidden', { configurable: true, get: () => hidden });
Object.defineProperty(document, 'visibilityState', {
configurable: true,
get: () => (hidden ? 'hidden' : 'visible'),
});
document.dispatchEvent(new Event('visibilitychange'));
};
set(true);
set(false);
});

test.describe('the focus stays where it was when the presentation is shown again (#15079)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('./revealjs/visibility-focus.html#/slide-1');
await expect(page.locator('.reveal.ready')).toBeAttached();
});

test('a link on a slide keeps the focus', async ({ page }) => {
const link = page.getByRole('link', { name: 'A link on slide-1' });
await link.focus();
await hideAndShow(page);
await expect(link).toBeFocused();
await page.keyboard.press('ArrowRight');
await expect(page).toHaveURL(/#\/slide-2$/);
});

test('a frame hands the focus to the deck, where keys reach reveal.js', async ({ page }) => {
await page.frameLocator('iframe').getByRole('button', { name: 'Button in a frame' }).focus();
await hideAndShow(page);
await expect(page.locator('.reveal')).toBeFocused();
await page.keyboard.press('ArrowRight');
await expect(page).toHaveURL(/#\/slide-2$/);
});

test('the deck keeps the focus', async ({ page }) => {
const deck = page.locator('.reveal');
await deck.evaluate((element) => {
element.setAttribute('tabindex', '-1');
(element as HTMLElement).focus();
});
await hideAndShow(page);
await expect(deck).toBeFocused();
});

test('a focus on the body stays there', async ({ page }) => {
await hideAndShow(page);
await expect(page.locator('body')).toBeFocused();
});
});
Loading