Commit c326426a authored by Daniel Hosseinian's avatar Daniel Hosseinian Committed by Commit Bot

PDF Viewer Update: Implement thumbnail keyboard navigation

Allow keyboard focusing on thumbnails, and implement focused thumbnail
styling. The thumbnail bar is "focusable", but it forwards focus to the
thumbnail of the active page.

Focus can be changed among thumbnails using arrow keys. Tabbing while
focused on a thumbnail diverts focus out of the thumbnail bar.

Allow changing page of the PDF content using the enter and space keys.

Bug: 652400
Change-Id: Ie03a069d9feff2889723012810324803afad18b4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2434770
Commit-Queue: Lei Zhang <thestig@chromium.org>
Commit-Queue: Daniel Hosseinian <dhoss@chromium.org>
Reviewed-by: default avatardpapad <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#811999}
parent 1057c013
......@@ -160,6 +160,8 @@ js_library("viewer-thumbnail-bar") {
deps = [
":viewer-thumbnail",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
"//ui/webui/resources/js:assert.m",
"//ui/webui/resources/js/cr/ui:focus_outline_manager.m",
]
}
......
......@@ -84,8 +84,9 @@
</div>
</div>
<div id="content">
<viewer-thumbnail-bar id="thumbnail-bar" hidden="[[!thumbnailView_]]"
active-page="[[activePage]]" doc-length="[[docLength]]">
<viewer-thumbnail-bar id="thumbnail-bar" tabindex="0"
hidden="[[!thumbnailView_]]" active-page="[[activePage]]"
doc-length="[[docLength]]">
</viewer-thumbnail-bar>
<viewer-document-outline id="outline" hidden="[[thumbnailView_]]"
bookmarks="[[bookmarks]]">
......
......@@ -14,8 +14,8 @@
<div id="thumbnails">
<template is="dom-repeat" items="[[pageNumbers_]]"
on-dom-change="onDomChange_">
<viewer-thumbnail is-active="[[isActivePage_(item, activePage)]]"
page-number="[[item]]">
<viewer-thumbnail tabindex="0"
is-active="[[isActivePage_(item, activePage)]]" page-number="[[item]]">
</viewer-thumbnail>
</template>
</div>
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import {assert} from 'chrome://resources/js/assert.m.js';
import {FocusOutlineManager} from 'chrome://resources/js/cr/ui/focus_outline_manager.m.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {ViewerThumbnailElement} from './viewer-thumbnail.js';
......@@ -33,6 +34,9 @@ export class ViewerThumbnailBarElement extends PolymerElement {
ready() {
super.ready();
this.addEventListener('focus', this.onFocus_);
this.addEventListener('keydown', this.onKeydown_);
const thumbnailsDiv = this.shadowRoot.querySelector('#thumbnails');
assert(thumbnailsDiv);
......@@ -60,6 +64,8 @@ export class ViewerThumbnailBarElement extends PolymerElement {
// are one standard finger swipe away.
rootMargin: '100% 0%',
});
FocusOutlineManager.forDocument(document);
}
/**
......@@ -85,6 +91,57 @@ export class ViewerThumbnailBarElement extends PolymerElement {
this.intersectionObserver_.observe(thumbnail);
});
}
/**
* Forwards focus to a thumbnail when tabbing.
* @private
*/
onFocus_() {
// Ignore focus triggered by mouse to allow the focus to go straight to the
// thumbnail being clicked.
const focusOutlineManager = FocusOutlineManager.forDocument(document);
if (!focusOutlineManager.visible) {
return;
}
// Change focus to the thumbnail of the active page.
const activeThumbnail =
this.shadowRoot.querySelector('viewer-thumbnail[is-active]');
if (activeThumbnail) {
activeThumbnail.focus();
return;
}
// Otherwise change to the first thumbnail, if there is one.
const firstThumbnail = this.shadowRoot.querySelector('viewer-thumbnail');
if (!firstThumbnail) {
return;
}
firstThumbnail.focus();
}
/**
* @param {!Event} e
* @private
*/
onKeydown_(e) {
const keyboardEvent = /** @type {!KeyboardEvent} */ (e);
if (keyboardEvent.key === 'Tab') {
// On shift+tab, first redirect focus from the thumbnails to:
// 1) Avoid focusing on the thumbnail bar.
// 2) Focus to the element before the thumbnail bar from any thumbnail.
if (e.shiftKey) {
this.focus();
return;
}
// On tab, first redirect focus to the last thumbnail to focus to the
// element after the thumbnail bar from any thumbnail.
this.shadowRoot.querySelector('viewer-thumbnail:last-of-type').focus({
preventScroll: true
});
}
}
}
customElements.define(ViewerThumbnailBarElement.is, ViewerThumbnailBarElement);
<style>
:host {
--focus-border-color: var(--google-blue-refresh-300);
display: block;
}
:host(:focus) {
outline: none;
}
#thumbnail {
/** TODO(crbug.com/652400): Change inactive background color. */
--inactive-background-color: black;
......@@ -13,6 +18,10 @@
margin-inline-start: auto;
}
:host(:focus) #thumbnail {
box-shadow: 0 0 0 2px var(--focus-border-color);
}
:host([is-active]) #thumbnail {
--active-background-color: white;
background-color: var(--active-background-color);
......
......@@ -35,6 +35,12 @@ export class ViewerThumbnailElement extends PolymerElement {
};
}
constructor() {
super();
this.addEventListener('keydown', this.onKeydown_);
}
/** @param {!ImageData} imageData */
set image(imageData) {
const canvas = this.shadowRoot.querySelector('canvas');
......@@ -72,6 +78,20 @@ export class ViewerThumbnailElement extends PolymerElement {
}
}
/** @private */
focusThumbnailNext_() {
if (this.nextElementSibling) {
this.nextElementSibling.focus();
}
}
/** @private */
focusThumbnailPrev_() {
if (this.previousElementSibling) {
this.previousElementSibling.focus();
}
}
/** @private */
onClick_() {
this.dispatchEvent(new CustomEvent('change-page', {
......@@ -80,6 +100,34 @@ export class ViewerThumbnailElement extends PolymerElement {
composed: true
}));
}
/**
* @param {!Event} e
* @private
*/
onKeydown_(e) {
const keyboardEvent = /** @type {!KeyboardEvent} */ (e);
switch (keyboardEvent.key) {
case 'ArrowDown':
case 'ArrowRight':
// Prevent default arrow scroll behavior.
keyboardEvent.preventDefault();
this.focusThumbnailNext_();
break;
case 'ArrowUp':
case 'ArrowLeft':
// Prevent default arrow scroll behavior.
keyboardEvent.preventDefault();
this.focusThumbnailPrev_();
break;
case 'Enter':
case ' ':
// Prevent default space scroll behavior.
keyboardEvent.preventDefault();
this.onClick_();
break;
}
}
}
customElements.define(ViewerThumbnailElement.is, ViewerThumbnailElement);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment