Commit 9c953d1c authored by Yue Cen's avatar Yue Cen Committed by Commit Bot

Fast app reinstall: Fix A11y issues

- Screen reader will read the app name when the chip is focused.
- Navigate chips using tab.
- Toggle app to install / not install with space or enter key.
- Screen reader will read the title of recommend-apps and app-dowloading
screens.

Bug: 885219
Change-Id: I5ad9f79b33ae8a55fe4f5240692261e346461a22
Reviewed-on: https://chromium-review.googlesource.com/1234237Reviewed-by: default avatarAlexander Alekseev <alemate@chromium.org>
Commit-Queue: Yue Cen <rsgingerrs@chromium.org>
Cr-Commit-Position: refs/heads/master@{#593670}
parent 08512341
...@@ -2,33 +2,47 @@ ...@@ -2,33 +2,47 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
var selectedApps = /** @dict */ {};
function generateContents(appIcon, appTitle, appPackageName) { function generateContents(appIcon, appTitle, appPackageName) {
var doc = document; const doc = document;
var recommendAppsContainer = doc.getElementById('recommend-apps-container'); const recommendAppsContainer = doc.getElementById('recommend-apps-container');
var item = doc.createElement('div'); const item = doc.createElement('div');
item.classList.add('item'); item.classList.add('item');
item.classList.add('checked'); item.classList.add('checked');
item.setAttribute('data-packagename', appPackageName); item.setAttribute('data-packagename', appPackageName);
var imagePicker = doc.createElement('div'); const imagePicker = doc.createElement('div');
imagePicker.classList.add('image-picker'); imagePicker.classList.add('image-picker');
imagePicker.addEventListener('click', toggleCheckStatus); imagePicker.addEventListener('click', toggleCheckStatus_);
item.appendChild(imagePicker); item.appendChild(imagePicker);
var chip = doc.createElement('div'); const chip = doc.createElement('div');
chip.classList.add('chip'); chip.classList.add('chip');
chip.addEventListener('mousedown', addRippleCircle); chip.tabIndex = 0;
chip.addEventListener('mouseup', toggleCheckStatus); chip.addEventListener('mousedown', addRippleCircle_);
chip.addEventListener('animationend', removeRippleCircle); chip.addEventListener('mouseup', toggleCheckStatus_);
chip.addEventListener('animationend', removeRippleCircle_);
// Add keyboard events
let keyEventFired = false;
chip.addEventListener('keydown', function(e) {
if (!keyEventFired && isConfirmKey_(e))
addRippleCircle_(e);
keyEventFired = true;
});
chip.addEventListener('keyup', function(e) {
if (isConfirmKey_(e)) {
toggleCheckStatus_(e);
keyEventFired = false;
}
});
item.appendChild(chip); item.appendChild(chip);
var img = doc.createElement('img'); const img = doc.createElement('img');
img.classList.add('app-icon'); img.classList.add('app-icon');
img.setAttribute('src', decodeURIComponent(appIcon)); img.setAttribute('src', decodeURIComponent(appIcon));
var title = doc.createElement('span'); const title = doc.createElement('span');
title.classList.add('app-title'); title.classList.add('app-title');
title.innerHTML = appTitle; title.innerHTML = appTitle;
...@@ -38,47 +52,59 @@ function generateContents(appIcon, appTitle, appPackageName) { ...@@ -38,47 +52,59 @@ function generateContents(appIcon, appTitle, appPackageName) {
recommendAppsContainer.appendChild(item); recommendAppsContainer.appendChild(item);
} }
// Add a layer on top of the chip to create the ripple effect. /**
function addRippleCircle(e) { * Add a layer on top of the chip to create the ripple effect.
let chip = e.currentTarget; * @param {!Event} e
let item = chip.parentNode; * @private
let offsetX = e.pageX - item.offsetLeft; */
let offsetY = e.pageY - item.offsetTop; function addRippleCircle_(e) {
const chip = e.currentTarget;
const item = chip.parentNode;
const offsetX = e.pageX - item.offsetLeft;
const offsetY = e.pageY - item.offsetTop;
chip.style.setProperty('--x', offsetX); chip.style.setProperty('--x', offsetX);
chip.style.setProperty('--y', offsetY); chip.style.setProperty('--y', offsetY);
chip.innerHTML += '<div class="ripple"></div>'; chip.innerHTML += '<div class="ripple"></div>';
} }
// After the animation ends, remove the ripple layer. /**
function removeRippleCircle(e) { * After the animation ends, remove the ripple layer.
let chip = e.currentTarget; * @param {!Event} e
let rippleLayers = chip.querySelectorAll('.ripple'); * @private
for (let rippleLayer of rippleLayers) { */
function removeRippleCircle_(e) {
const chip = e.currentTarget;
const rippleLayers = chip.querySelectorAll('.ripple');
for (const rippleLayer of rippleLayers) {
if (rippleLayer.className === 'ripple') { if (rippleLayer.className === 'ripple') {
rippleLayer.remove(); rippleLayer.remove();
} }
} }
} }
// Toggle the check status of an app. If an app is selected, add the "checked" /**
// class so that the checkmark is visible. Otherwise, remove the checked class. * Toggle the check status of an app. If an app is selected, add the "checked"
function toggleCheckStatus(e) { * lass so that the checkmark is visible. Otherwise, remove the checked class.
var item = e.currentTarget.parentNode; * @param {!Event} e
* @private
*/
function toggleCheckStatus_(e) {
const item = e.currentTarget.parentNode;
item.classList.toggle('checked'); item.classList.toggle('checked');
} }
function getSelectedPackages() { function getSelectedPackages() {
var selectedPackages = []; const selectedPackages = [];
var checkedItems = document.getElementsByClassName('checked'); const checkedItems = document.getElementsByClassName('checked');
for (var checkedItem of checkedItems) { for (const checkedItem of checkedItems) {
selectedPackages.push(checkedItem.dataset.packagename); selectedPackages.push(checkedItem.dataset.packagename);
} }
return selectedPackages; return selectedPackages;
} }
function toggleScrollShadow(container) { function toggleScrollShadow_(container) {
const shadowThreshold = 5; const shadowThreshold = 5;
var doc = document; const doc = document;
doc.getElementById('scroll-top') doc.getElementById('scroll-top')
.classList.toggle('shadow', container.scrollTop > shadowThreshold); .classList.toggle('shadow', container.scrollTop > shadowThreshold);
doc.getElementById('scroll-bottom') doc.getElementById('scroll-bottom')
...@@ -89,14 +115,21 @@ function toggleScrollShadow(container) { ...@@ -89,14 +115,21 @@ function toggleScrollShadow(container) {
shadowThreshold); shadowThreshold);
} }
// Add the scroll shadow effect. This contains two parts. First initialize the /**
// effect after all the contents have been generated. Then attach it to the * Add the scroll shadow effect. This contains two parts. First initialize the
// onscroll event. * effect after all the contents have been generated. Then attach it to the
* onscroll event.
*/
function addScrollShadowEffect() { function addScrollShadowEffect() {
var doc = document; const doc = document;
var container = doc.getElementById('recommend-apps-container'); const container = doc.getElementById('recommend-apps-container');
toggleScrollShadow(container); toggleScrollShadow_(container);
container.onscroll = function() { container.onscroll = function() {
toggleScrollShadow(this); toggleScrollShadow_(this);
}; };
} }
\ No newline at end of file
function isConfirmKey_(e) {
return e.keyCode === 13 // Enter
|| e.keyCode === 32; // Space
}
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
<link rel="stylesheet" href="app_downloading.css"> <link rel="stylesheet" href="app_downloading.css">
<link rel="stylesheet" href="oobe_flex_layout.css"> <link rel="stylesheet" href="oobe_flex_layout.css">
<oobe-dialog id="app-downloading-dialog" role="dialog" has-buttons <oobe-dialog id="app-downloading-dialog" role="dialog" has-buttons
aria-label$="[[getDialogTitleA11yString_(numOfApps)]]"
no-footer-padding> no-footer-padding>
<iron-icon src="chrome://oobe/playstore.svg" slot="oobe-icon"> <iron-icon src="chrome://oobe/playstore.svg" slot="oobe-icon">
</iron-icon> </iron-icon>
......
...@@ -36,5 +36,14 @@ Polymer({ ...@@ -36,5 +36,14 @@ Polymer({
/** @private */ /** @private */
hasSingleApp_: function(numOfApps) { hasSingleApp_: function(numOfApps) {
return numOfApps === 1; return numOfApps === 1;
} },
/** @private */
getDialogTitleA11yString_: function(numOfApps) {
if (this.hasSingleApp_(numOfApps)) {
return this.i18n('appDownloadingScreenTitleSingular');
} else {
return this.i18n('appDownloadingScreenTitlePlural', numOfApps);
}
},
}); });
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