Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vpat 81: instructions for keyboard interface #142

Merged
merged 8 commits into from
Oct 12, 2024
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 src/common/components/reader-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ function View(props) {
onFindNext={handleFindNext}
onFindPrevious={handleFindPrevious}
onAddAnnotation={props.onAddAnnotation}
tools={props.tools}
/>
}
</div>
Expand Down
17 changes: 16 additions & 1 deletion src/common/components/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useIntl } from 'react-intl';
import cx from 'classnames';
import CustomSections from './common/custom-sections';
import { ReaderContext } from '../reader';

import { isMac } from '../lib/utilities';
import { IconColor20 } from './common/icons';

import IconSidebar from '../../../res/icons/20/sidebar.svg';
Expand Down Expand Up @@ -72,6 +72,15 @@ function Toolbar(props) {
}
}

// Add aria instructions on how to add annotations with keyboard
function _constructAriaDecription(number) {
// Cmd/Alt+Option+1/2
let underlineOrHighlight = number <= 2;
let instruction = intl.formatMessage({ id: `pdfReader.a11y${underlineOrHighlight ? 'Textual' : ''}AnnotationInstruction` });
let modifier = intl.formatMessage({ id: `pdfReader.a11yAnnotationModifier${isMac() ? 'Mac' : ''}` });
return `${instruction} ${modifier} - ${number}`;
}

return (
<div className="toolbar" data-tabstop={1}>
<div className="start">
Expand Down Expand Up @@ -177,6 +186,7 @@ function Toolbar(props) {
title={intl.formatMessage({ id: 'pdfReader.highlightText' })}
disabled={props.readOnly}
onClick={() => handleToolClick('highlight')}
aria-description={_constructAriaDecription(1)}
><IconHighlight/></button>
{ (platform !== 'web' || ['epub', 'snapshot'].includes(props.type)) && (
<button
Expand All @@ -185,6 +195,7 @@ function Toolbar(props) {
title={intl.formatMessage({ id: 'pdfReader.underlineText' })}
disabled={props.readOnly}
onClick={() => handleToolClick('underline')}
aria-description={_constructAriaDecription(2)}
><IconUnderline/></button>
)}
<button
Expand All @@ -195,6 +206,7 @@ function Toolbar(props) {
title={intl.formatMessage({ id: 'pdfReader.addNote' })}
disabled={props.readOnly}
onClick={() => handleToolClick('note')}
aria-description={_constructAriaDecription(3)}
><IconNote/></button>
{props.type === 'pdf' && platform !== 'web' && (
<button
Expand All @@ -203,6 +215,7 @@ function Toolbar(props) {
title={intl.formatMessage({ id: 'pdfReader.addText' })}
disabled={props.readOnly}
onClick={() => handleToolClick('text')}
aria-description={_constructAriaDecription(4)}
><IconText/></button>
)}
{props.type === 'pdf' && (
Expand All @@ -212,6 +225,7 @@ function Toolbar(props) {
title={intl.formatMessage({ id: 'pdfReader.selectArea' })}
disabled={props.readOnly}
onClick={() => handleToolClick('image')}
aria-description={_constructAriaDecription(5)}
><IconImage/></button>
)}
{props.type === 'pdf' && (
Expand All @@ -221,6 +235,7 @@ function Toolbar(props) {
title={intl.formatMessage({ id: 'pdfReader.draw' })}
disabled={props.readOnly}
onClick={() => handleToolClick('ink')}
aria-description={intl.formatMessage({ id: 'pdfReader.a11yAnnotationNotSupported' })}
><IconInk/></button>
)}
<div className="divider"/>
Expand Down
17 changes: 13 additions & 4 deletions src/common/components/view-popup/find-popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { DEBOUNCE_FIND_POPUP_INPUT } from '../../defines';
import IconChevronUp from '../../../../res/icons/20/chevron-up.svg';
import IconChevronDown from '../../../../res/icons/20/chevron-down.svg';
import IconClose from '../../../../res/icons/20/x.svg';
import { getCodeCombination, getKeyCombination } from '../../lib/utilities';
import { getCodeCombination, getKeyCombination, isMac } from '../../lib/utilities';

function FindPopup({ params, onChange, onFindNext, onFindPrevious, onAddAnnotation }) {
function FindPopup({ params, onChange, onFindNext, onFindPrevious, onAddAnnotation, tools }) {
const intl = useIntl();
const inputRef = useRef();
const preventInputRef = useRef(false);
Expand Down Expand Up @@ -72,13 +72,17 @@ function FindPopup({ params, onChange, onFindNext, onFindPrevious, onAddAnnotati
else if (code === 'Ctrl-Alt-Digit1') {
preventInputRef.current = true;
if (params.result?.annotation) {
onAddAnnotation({ ...params.result.annotation, type: 'highlight' }, true);
onAddAnnotation({ ...params.result.annotation, type: 'highlight', color: tools['highlight'].color }, true);
// Close popup after adding annotation
onChange({ ...params, popupOpen: false, active: false, result: null });
}
}
else if (code === 'Ctrl-Alt-Digit2') {
preventInputRef.current = true;
if (params.result?.annotation) {
onAddAnnotation({ ...params.result.annotation, type: 'underline' }, true);
onAddAnnotation({ ...params.result.annotation, type: 'underline', color: tools['underline'].color }, true);
// Close popup after adding annotation
onChange({ ...params, popupOpen: false, active: false, result: null });
}
}
}
Expand Down Expand Up @@ -109,6 +113,11 @@ function FindPopup({ params, onChange, onFindNext, onFindPrevious, onAddAnnotati
title={intl.formatMessage({ id: 'pdfReader.find' })}
className="toolbar-text-input"
placeholder="Find in document…"
aria-description={
intl.formatMessage({ id: 'pdfReader.a11yTextualAnnotationFindInDocumentInstruction' })
+ ` ${intl.formatMessage({ id: 'pdfReader.a11yAnnotationModifierControl' })} - ${intl.formatMessage({ id: `pdfReader.a11yAnnotationModifier${isMac() ? 'Mac' : ''}` })} - ${1}`
+ `, ${intl.formatMessage({ id: 'pdfReader.a11yAnnotationModifierControl' })} - ${intl.formatMessage({ id: `pdfReader.a11yAnnotationModifier${isMac() ? 'Mac' : ''}` })} - ${2}`
}
value={query !== null ? query : params.query}
tabIndex="-1"
data-tabstop={1}
Expand Down
42 changes: 41 additions & 1 deletion src/common/reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@ class Reader {
onResizeSplitView={this.setSplitViewSize.bind(this)}
onAddAnnotation={(annotation, select) => {
annotation = this._annotationManager.addAnnotation(annotation);
// Tell screen readers the annotation was added after focus is settled
setTimeout(() => {
this.setA11yMessage(this._getString(`pdfReader.a11yAnnotationCreated.${annotation.type}`));
}, 100);
if (select) {
this.setSelectedAnnotations([annotation.id]);
} else {
Expand Down Expand Up @@ -307,6 +311,7 @@ class Reader {
onToggleContextPane={this._onToggleContextPane}
onChangeTextSelectionAnnotationMode={this.setTextSelectionAnnotationMode.bind(this)}
ref={this._readerRef}
tools={this._tools}
/>
</ReaderContext.Provider>
</IntlProvider>
Expand Down Expand Up @@ -781,6 +786,10 @@ class Reader {

let onAddAnnotation = (annotation, select) => {
annotation = this._annotationManager.addAnnotation(annotation);
// Tell screen readers the annotation was added after focus is settled
setTimeout(() => {
this.setA11yMessage(this._getString(`pdfReader.a11yAnnotationCreated.${annotation.type}`));
}, 100);
if (select) {
this.setSelectedAnnotations([annotation.id], true);
}
Expand Down Expand Up @@ -866,6 +875,10 @@ class Reader {
let onFocusAnnotation = (annotation) => {
if (!annotation) return;
// Announce the current annotation to screen readers
if (annotation.type == 'external-link') {
this.setA11yMessage(annotation.url);
return;
}
let annotationType = this._getString(`pdfReader.${annotation.type}Annotation`);
let annotationContent = `${annotationType}. ${annotation.text || annotation.comment}`;
this.setA11yMessage(annotationContent);
Expand Down Expand Up @@ -1178,10 +1191,11 @@ class Reader {
this._updateState({ selectedAnnotationIDs: ids });

// Don't navigate to annotation or focus comment if opening a context menu
// unless it is a note (so that one can type after creating it via shortcut, same as with text annotation)
if (!triggeringEvent || triggeringEvent.button !== 2) {
if (triggeredFromView) {
if (['note', 'highlight', 'underline'].includes(annotation.type)
&& !annotation.comment && (!triggeringEvent || !('key' in triggeringEvent))) {
&& !annotation.comment && (!triggeringEvent || annotation.type === 'note')) {
this._enableAnnotationDeletionFromComment = true;
setTimeout(() => {
let content;
Expand All @@ -1199,6 +1213,32 @@ class Reader {
this._lastView.navigate({ annotationID: annotation.id });
}
}
// After a small delay for focus to settle, announce to screen readers that annotation
// is selected and how one can manipulate it
setTimeout(() => {
let a11yAnnouncement = this._getString(`pdfReader.a11yAnnotationSelected.${annotation.type}`);
if (document.querySelector('.annotation-popup')) {
// add note that popup is opened
a11yAnnouncement += ' ' + this._getString('pdfReader.a11yAnnotationPopupAppeared');
}
if (['highlight', 'underline'].includes(annotation.type)) {
// tell how to edit highlight/underline annotations
a11yAnnouncement += ' ' + this._getString('pdfReader.a11yEditTextAnnotation') + ' ' + this._getString(`pdfReader.a11yAnnotationModifier${isMac() ? 'Mac' : ''}`);
}
else if (['note', 'text', 'image'].includes(annotation.type)) {
// tell how to move and resize remaining types
a11yAnnouncement += ' ' + this._getString('pdfReader.a11yMoveAnnotation');
if (['text', 'image'].includes(annotation.type)) {
a11yAnnouncement += ' ' + this._getString('pdfReader.a11yResizeAnnotation');
}
}

// only announce if the content view is focused. E.g. if comment in
// sidebar has focus, say nothing as it will not be relevant
if (document.activeElement.nodeName === 'IFRAME') {
this.setA11yMessage(a11yAnnouncement);
}
}, 100);
}
}
// Smoothly scroll to the annotation, if only one was selected
Expand Down
25 changes: 24 additions & 1 deletion src/en-us.strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,5 +190,28 @@ export default {
'pdfReader.convertToHighlight': 'Convert to Highlight',
'pdfReader.convertToUnderline': 'Convert to Underline',
'pdfReader.size': 'Size',
'pdfReader.merge': 'Merge'
'pdfReader.merge': 'Merge',
'pdfReader.a11yAnnotationModifierMac': 'Option',
'pdfReader.a11yAnnotationModifier': 'Alt',
'pdfReader.a11yAnnotationModifierControl': 'Control',
'pdfReader.a11yTextualAnnotationFindInDocumentInstruction': 'To turn a search result into a highlight or underline annotation, press',
'pdfReader.a11yTextualAnnotationInstruction': 'To annotate text via the keyboard, first use “Find in Document” to locate the phrase. Then, to turn the search result into an annotation, press Control -',
dstillman marked this conversation as resolved.
Show resolved Hide resolved
'pdfReader.a11yAnnotationInstruction': 'To add this annotation to the document, focus the document and press Control -',
'pdfReader.a11yAnnotationNotSupported': 'This annotation type cannot be created via the keyboard.',
'pdfReader.a11yMoveAnnotation': 'Use the arrow keys to move the annotation.',
'pdfReader.a11yEditTextAnnotation': 'To move the end of the text annotation, use the left/right arrow keys while holding Shift. To move the start of the annotation, use the arrow keys while holding Shift -',
'pdfReader.a11yResizeAnnotation': 'To resize the annotation, use the arrow keys while holding Shift.',
'pdfReader.a11yAnnotationPopupAppeared': 'Use Tab to navigate the annotation popup.',

"pdfReader.a11yAnnotationCreated.highlight": "Highlight annotation created",
"pdfReader.a11yAnnotationCreated.underline": "Underline annotation created",
"pdfReader.a11yAnnotationCreated.note": "Note annotation created",
"pdfReader.a11yAnnotationCreated.text": "Text annotation created",
"pdfReader.a11yAnnotationCreated.image": "Image annotation created",

"pdfReader.a11yAnnotationSelected.highlight": "Highlight annotation selected",
"pdfReader.a11yAnnotationSelected.underline": "Underline annotation selected",
"pdfReader.a11yAnnotationSelected.note": "Note annotation selected",
"pdfReader.a11yAnnotationSelected.text": "Text annotation selected",
"pdfReader.a11yAnnotationSelected.image": "Image annotation selected"
};
Loading