diff --git a/src/common/components/reader-ui.js b/src/common/components/reader-ui.js index 8a8402c7..9d28b790 100644 --- a/src/common/components/reader-ui.js +++ b/src/common/components/reader-ui.js @@ -80,6 +80,7 @@ function View(props) { onFindNext={handleFindNext} onFindPrevious={handleFindPrevious} onAddAnnotation={props.onAddAnnotation} + tools={props.tools} /> } diff --git a/src/common/components/toolbar.js b/src/common/components/toolbar.js index d491ddeb..495b02fa 100644 --- a/src/common/components/toolbar.js +++ b/src/common/components/toolbar.js @@ -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'; @@ -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 (
@@ -177,6 +186,7 @@ function Toolbar(props) { title={intl.formatMessage({ id: 'pdfReader.highlightText' })} disabled={props.readOnly} onClick={() => handleToolClick('highlight')} + aria-description={_constructAriaDecription(1)} > { (platform !== 'web' || ['epub', 'snapshot'].includes(props.type)) && ( )} {props.type === 'pdf' && platform !== 'web' && ( )} {props.type === 'pdf' && ( @@ -212,6 +225,7 @@ function Toolbar(props) { title={intl.formatMessage({ id: 'pdfReader.selectArea' })} disabled={props.readOnly} onClick={() => handleToolClick('image')} + aria-description={_constructAriaDecription(5)} > )} {props.type === 'pdf' && ( @@ -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' })} > )}
diff --git a/src/common/components/view-popup/find-popup.js b/src/common/components/view-popup/find-popup.js index 22368bd3..ff969581 100644 --- a/src/common/components/view-popup/find-popup.js +++ b/src/common/components/view-popup/find-popup.js @@ -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); @@ -75,13 +75,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 }); } } } @@ -112,6 +116,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} diff --git a/src/common/reader.js b/src/common/reader.js index cfc3bdfd..0a0eda0b 100644 --- a/src/common/reader.js +++ b/src/common/reader.js @@ -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 { @@ -307,6 +311,7 @@ class Reader { onToggleContextPane={this._onToggleContextPane} onChangeTextSelectionAnnotationMode={this.setTextSelectionAnnotationMode.bind(this)} ref={this._readerRef} + tools={this._tools} /> @@ -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); } @@ -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); @@ -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; @@ -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 diff --git a/src/en-us.strings.js b/src/en-us.strings.js index 507f3dc7..b0ef361a 100644 --- a/src/en-us.strings.js +++ b/src/en-us.strings.js @@ -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 -', + '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" };