Skip to content

Commit

Permalink
Files app: Enable keyboard control of file list sort direction
Browse files Browse the repository at this point in the history
Adds a tab stop for the sort icon in the file list. This allows a user
to press 'Enter' to change the sort direction.

Note, the file list header is regenerated each time the sort is changed
(existing behaviour). If 'focus-outline-visible' is set on the root
document it indicates the keyboard is being used for navigation, we
use this and current focus position to decide if the sort button
should be focused after a sort of the file list.

Bug: b/255519075
Tests: browser_tests --gtest_filter="File*fileListSortWithKeyboard"
Change-Id: I110d8869613e12dc4c70b6d35f2f1f78b8270692
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4109950
Reviewed-by: Wenbo Jie <wenbojie@chromium.org>
Commit-Queue: Alex Danilo <adanilo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1084838}
  • Loading branch information
adanilo authored and Chromium LUCI CQ committed Dec 19, 2022
1 parent 41c6e97 commit 7aa30e9
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 2 deletions.
Expand Up @@ -1588,6 +1588,7 @@ WRAPPED_INSTANTIATE_TEST_SUITE_P(
::testing::Values(TestCase("fileListAriaAttributes"),
TestCase("fileListFocusFirstItem"),
TestCase("fileListSelectLastFocusedItem"),
TestCase("fileListSortWithKeyboard"),
TestCase("fileListKeyboardSelectionA11y"),
TestCase("fileListMouseSelectionA11y"),
TestCase("fileListDeleteMultipleFiles"),
Expand Down
Expand Up @@ -2097,6 +2097,7 @@ body.files-ng #list-container .table-header-cell:first-child
.table-header-label .sort-icon {
--cr-icon-button-fill-color: var(--cros-icon-color-secondary);
--cr-icon-button-icon-size: 16px;
--cr-icon-button-focus-outline-color: var(--cros-focus-ring-color);
--cr-icon-button-hover-background-color: var(--cros-ripple-color);
--cr-icon-button-size: 32px;
border-radius: 50%;
Expand Down
Expand Up @@ -2030,6 +2030,7 @@ body.files-ng #list-container .table-header-cell:first-child
.table-header-label .sort-icon {
--cr-icon-button-fill-color: var(--cros-icon-color-secondary);
--cr-icon-button-icon-size: 16px;
--cr-icon-button-focus-outline-color: var(--cros-focus-ring-color);
--cr-icon-button-hover-background-color: var(--cros-ripple-color);
--cr-icon-button-size: 32px;
border-radius: 50%;
Expand Down
16 changes: 14 additions & 2 deletions ui/file_manager/file_manager/foreground/js/ui/file_table.js
Expand Up @@ -291,8 +291,20 @@ export function renderHeader_(table) {
const icon = document.createElement('cr-icon-button');
const iconName = sortOrder === 'desc' ? 'up' : 'down';
icon.setAttribute('iron-icon', `files16:arrow_${iconName}_small`);
icon.setAttribute('tabindex', '-1');
icon.setAttribute('aria-hidden', 'true');
// If we're the sorting column make the icon a tab target.
if (isSorted) {
icon.id = 'sort-direction-button';
icon.setAttribute('tabindex', '0');
icon.setAttribute('aria-hidden', 'false');
if (sortOrder === 'asc') {
icon.setAttribute('aria-label', str('COLUMN_ASC_SORT_MESSAGE'));
} else {
icon.setAttribute('aria-label', str('COLUMN_DESC_SORT_MESSAGE'));
}
} else {
icon.setAttribute('tabindex', '-1');
icon.setAttribute('aria-hidden', 'true');
}
icon.classList.add('sort-icon', 'no-overlap');

container.classList.toggle('not-sorted', !isSorted);
Expand Down
12 changes: 12 additions & 0 deletions ui/file_manager/file_manager/foreground/js/ui/table/table.js
Expand Up @@ -296,6 +296,18 @@ export class Table {
*/
handleSorted_(e) {
this.header_.redraw();
// If we have 'focus-outline-visible' on the root HTML element and focus
// has reverted to the body element it means this sort header creation
// was the result of a keyboard action so set focus to the (newly
// recreated) sort button in that case.
if (document.querySelector('html.focus-outline-visible') &&
(document.activeElement instanceof HTMLBodyElement)) {
const sortButton =
this.header_.querySelector('cr-icon-button[tabindex="0"]');
if (sortButton) {
sortButton.focus();
}
}
this.onDataModelSorted();
}

Expand Down
50 changes: 50 additions & 0 deletions ui/file_manager/integration_tests/file_manager/file_list.js
Expand Up @@ -144,6 +144,56 @@ testcase.fileListSelectLastFocusedItem = async () => {
chrome.test.assertEq(2, fileRows.indexOf(selectedRows[0]));
};

/**
* Tests that after a multiple selection, canceling the selection and using
* Tab to focus the files list it selects the item that was last focused.
*/
testcase.fileListSortWithKeyboard = async () => {
const appId = await setupAndWaitUntilReady(
RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);

// Send shift-Tab key to tab into sort button.
const result = await sendTestMessage({name: 'dispatchTabKey', shift: true});
chrome.test.assertEq(result, 'tabKeyDispatched', 'Tab key dispatch failed');
// Check: sort button has focus.
let focusedElement =
await remoteCall.callRemoteTestUtil('getActiveElement', appId, []);
// Check: button is showing down arrow.
chrome.test.assertTrue(
focusedElement['attributes']['iron-icon'] === 'files16:arrow_down_small');
// Check: aria-label tells us to click to sort ascending.
chrome.test.assertTrue(
focusedElement['attributes']['aria-label'] ===
'Click to sort the column in ascending order.');
// Press 'enter' on the sort button.
const key = ['cr-icon-button[tabindex="0"]', 'Enter', false, false, false];
chrome.test.assertTrue(
await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, key));
// Get the state of the (focused) sort button.
focusedElement =
await remoteCall.callRemoteTestUtil('getActiveElement', appId, []);
// Check: button is showing up arrow.
chrome.test.assertTrue(
focusedElement['attributes']['iron-icon'] === 'files16:arrow_up_small');
// Check: aria-label tells us to click to sort descending.
chrome.test.assertTrue(
focusedElement['attributes']['aria-label'] ===
'Click to sort the column in descending order.');
// Press 'enter' key on the sort button again.
chrome.test.assertTrue(
await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, key));
// Get the state of the (focused) sort button.
focusedElement =
await remoteCall.callRemoteTestUtil('getActiveElement', appId, []);
// Check: button is showing up arrow.
chrome.test.assertTrue(
focusedElement['attributes']['iron-icon'] === 'files16:arrow_down_small');
// Check: aria-label tells us to click to sort descending.
chrome.test.assertTrue(
focusedElement['attributes']['aria-label'] ===
'Click to sort the column in ascending order.');
};

/**
* Verifies the total number of a11y messages and asserts the latest message
* is the expected one.
Expand Down
7 changes: 7 additions & 0 deletions ui/file_manager/integration_tests/file_manager/tab_index.js
Expand Up @@ -76,6 +76,8 @@ testcase.tabindexFocus = async () => {
await remoteCall.checkNextTabFocus(appId, 'drive-learn-more-button'));
chrome.test.assertTrue(
await remoteCall.checkNextTabFocus(appId, 'dismiss-button'));
chrome.test.assertTrue(
await remoteCall.checkNextTabFocus(appId, 'sort-direction-button'));
chrome.test.assertTrue(
await remoteCall.checkNextTabFocus(appId, 'file-list'));
};
Expand Down Expand Up @@ -112,6 +114,8 @@ testcase.tabindexFocusDownloads = async () => {
await remoteCall.checkNextTabFocus(appId, 'gear-button'));
chrome.test.assertTrue(
await remoteCall.checkNextTabFocus(appId, 'dismiss-button'));
chrome.test.assertTrue(
await remoteCall.checkNextTabFocus(appId, 'sort-direction-button'));
chrome.test.assertTrue(
await remoteCall.checkNextTabFocus(appId, 'file-list'));
};
Expand Down Expand Up @@ -176,6 +180,8 @@ testcase.tabindexFocusDirectorySelected = async () => {
await remoteCall.checkNextTabFocus(appId, 'drive-learn-more-button'));
chrome.test.assertTrue(
await remoteCall.checkNextTabFocus(appId, 'dismiss-button'));
chrome.test.assertTrue(
await remoteCall.checkNextTabFocus(appId, 'sort-direction-button'));
chrome.test.assertTrue(
await remoteCall.checkNextTabFocus(appId, 'file-list'));

Expand Down Expand Up @@ -258,6 +264,7 @@ testcase.tabindexOpenDialogDownloads = async () => {
'sort-button',
'gear-button',
'dismiss-button',
'sort-direction-button',
'file-list',
];
return tabindexFocus(
Expand Down

0 comments on commit 7aa30e9

Please sign in to comment.