From 7d6ba7634086a584d8fc83fb0102e9d6be67a7c8 Mon Sep 17 00:00:00 2001
From: Andrew Serong <14988353+andrewserong@users.noreply.github.com>
Date: Wed, 23 Mar 2022 13:33:34 +1100
Subject: [PATCH] List View: Add multi-select support for shift + Home and End
keys (#39272)
* List View: Add multi-select support for shift + Home and End keys
* Skip announcing deselected blocks when using Home and End keys for the selection
* Try setting preventAnnouncement at the parent list view, and pass down to the select button
* Remove preventAnnouncement and move block description up to the grid cell level
* Add link to block title label, restore aria-expanded at the button level
* Update e2e tests to look for aria-selected attributes
* Try removing code that prevents the announcement of deselected blocks
* Add back in skipping announcing deselected blocks
---
.../list-view/block-select-button.js | 31 +----------
.../src/components/list-view/block.js | 54 +++++++++++++++----
.../list-view/use-block-selection.js | 17 +++++-
.../specs/editor/various/list-view.test.js | 44 ++++++++-------
4 files changed, 86 insertions(+), 60 deletions(-)
diff --git a/packages/block-editor/src/components/list-view/block-select-button.js b/packages/block-editor/src/components/list-view/block-select-button.js
index 452f01d30b3bd..ad6a05818afda 100644
--- a/packages/block-editor/src/components/list-view/block-select-button.js
+++ b/packages/block-editor/src/components/list-view/block-select-button.js
@@ -6,17 +6,14 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
-import { Button, VisuallyHidden } from '@wordpress/components';
-import { useInstanceId } from '@wordpress/compose';
+import { Button } from '@wordpress/components';
import { forwardRef } from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import BlockIcon from '../block-icon';
import useBlockDisplayInformation from '../use-block-display-information';
-import { getBlockPositionDescription } from './utils';
import BlockTitle from '../block-title';
import ListViewExpander from './expander';
import { SPACE, ENTER } from '@wordpress/keycodes';
@@ -25,29 +22,17 @@ function ListViewBlockSelectButton(
{
className,
block: { clientId },
- isSelected,
onClick,
onToggleExpanded,
- position,
- siblingBlockCount,
- level,
tabIndex,
onFocus,
onDragStart,
onDragEnd,
draggable,
- isExpanded,
},
ref
) {
const blockInformation = useBlockDisplayInformation( clientId );
- const instanceId = useInstanceId( ListViewBlockSelectButton );
- const descriptionId = `list-view-block-select-button__${ instanceId }`;
- const blockPositionDescription = getBlockPositionDescription(
- position,
- siblingBlockCount,
- level
- );
// The `href` attribute triggers the browser's native HTML drag operations.
// When the link is dragged, the element's outerHTML is set in DataTransfer object as text/html.
@@ -73,7 +58,6 @@ function ListViewBlockSelectButton(
) }
onClick={ onClick }
onKeyDown={ onKeyDownHandler }
- aria-describedby={ descriptionId }
ref={ ref }
tabIndex={ tabIndex }
onFocus={ onFocus }
@@ -81,7 +65,7 @@ function ListViewBlockSelectButton(
onDragEnd={ onDragEnd }
draggable={ draggable }
href={ `#block-${ clientId }` }
- aria-expanded={ isExpanded }
+ aria-hidden={ true }
>
@@ -91,18 +75,7 @@ function ListViewBlockSelectButton(
{ blockInformation.anchor }
) }
- { isSelected && (
-
- { __( '(selected block)' ) }
-
- ) }
-
- { blockPositionDescription }
-
>
);
}
diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js
index 0a432534c2550..71a1c42638157 100644
--- a/packages/block-editor/src/components/list-view/block.js
+++ b/packages/block-editor/src/components/list-view/block.js
@@ -10,6 +10,7 @@ import {
__experimentalTreeGridCell as TreeGridCell,
__experimentalTreeGridItem as TreeGridItem,
} from '@wordpress/components';
+import { useInstanceId } from '@wordpress/compose';
import { moreVertical } from '@wordpress/icons';
import {
useState,
@@ -32,6 +33,7 @@ import {
import ListViewBlockContents from './block-contents';
import BlockSettingsDropdown from '../block-settings-menu/block-settings-dropdown';
import { useListViewContext } from './context';
+import { getBlockPositionDescription } from './utils';
import { store as blockEditorStore } from '../../store';
import useBlockDisplayInformation from '../use-block-display-information';
@@ -49,6 +51,7 @@ function ListViewBlock( {
path,
isExpanded,
selectedClientIds,
+ preventAnnouncement,
} ) {
const cellRef = useRef( null );
const [ isHovered, setIsHovered ] = useState( false );
@@ -56,6 +59,31 @@ function ListViewBlock( {
const { toggleBlockHighlight } = useDispatch( blockEditorStore );
+ const blockInformation = useBlockDisplayInformation( clientId );
+ const instanceId = useInstanceId( ListViewBlock );
+ const descriptionId = `list-view-block-select-button__${ instanceId }`;
+ const blockPositionDescription = getBlockPositionDescription(
+ position,
+ siblingBlockCount,
+ level
+ );
+
+ const blockAriaLabel = blockInformation
+ ? sprintf(
+ // translators: %s: The title of the block. This string indicates a link to select the block.
+ __( '%s link' ),
+ blockInformation.title
+ )
+ : __( 'Link' );
+
+ const settingsAriaLabel = blockInformation
+ ? sprintf(
+ // translators: %s: The title of the block.
+ __( 'Options for %s block' ),
+ blockInformation.title
+ )
+ : __( 'Options' );
+
const {
__experimentalFeatures: withExperimentalFeatures,
__experimentalPersistentListViewFeatures: withExperimentalPersistentListViewFeatures,
@@ -155,15 +183,6 @@ function ListViewBlock( {
'has-single-cell': hideBlockActions,
} );
- const blockInformation = useBlockDisplayInformation( clientId );
- const settingsAriaLabel = blockInformation
- ? sprintf(
- // translators: %s: The title of the block.
- __( 'Options for %s block' ),
- blockInformation.title
- )
- : __( 'Options' );
-
// Only include all selected blocks if the currently clicked on block
// is one of the selected blocks. This ensures that if a user attempts
// to alter a block that isn't part of the selection, they're still able
@@ -186,11 +205,16 @@ function ListViewBlock( {
id={ `list-view-block-${ clientId }` }
data-block={ clientId }
isExpanded={ isExpanded }
+ aria-selected={ !! isSelected }
>
{ ( { ref, tabIndex, onFocus } ) => (
@@ -207,7 +231,14 @@ function ListViewBlock( {
onFocus={ onFocus }
isExpanded={ isExpanded }
selectedClientIds={ selectedClientIds }
+ preventAnnouncement={ preventAnnouncement }
/>
+
+ { blockPositionDescription }
+
) }
@@ -244,7 +275,10 @@ function ListViewBlock( {
) }
{ showBlockActions && (
-
+
{ ( { ref, tabIndex, onFocus } ) => (
1
+ ) {
+ return;
+ }
+
const selectionDiff = difference(
selectedBlocks,
updatedSelectedBlocks
diff --git a/packages/e2e-tests/specs/editor/various/list-view.test.js b/packages/e2e-tests/specs/editor/various/list-view.test.js
index 7ec97b9a14231..072f549214f49 100644
--- a/packages/e2e-tests/specs/editor/various/list-view.test.js
+++ b/packages/e2e-tests/specs/editor/various/list-view.test.js
@@ -72,8 +72,8 @@ describe( 'List view', () => {
await pressKeyWithModifier( 'access', 'o' );
// The last inserted paragraph block should be selected in List View.
- await page.waitForXPath(
- '//a[contains(., "Paragraph(selected block)")]'
+ await page.waitForSelector(
+ '.block-editor-list-view-block__contents-cell[aria-selected="true"][aria-label^="Paragraph"]'
);
// Go to the image block in list view.
@@ -115,8 +115,8 @@ describe( 'List view', () => {
await openListView();
// The last inserted paragraph block should be selected in List View.
- await page.waitForXPath(
- '//a[contains(., "Paragraph(selected block)")]'
+ await page.waitForSelector(
+ '.block-editor-list-view-block__contents-cell[aria-selected="true"][aria-label^="Paragraph"]'
);
// Paragraph options button.
@@ -134,8 +134,8 @@ describe( 'List view', () => {
await paragraphRemoveButton.click();
// Heading block should be selected as previous block.
- await page.waitForXPath(
- '//a[contains(., "Heading(selected block)")]'
+ await page.waitForSelector(
+ '.block-editor-list-view-block__contents-cell[aria-selected="true"][aria-label^="Heading"]'
);
} );
@@ -150,8 +150,8 @@ describe( 'List view', () => {
await openListView();
// The last inserted paragraph block should be selected in List View.
- await page.waitForXPath(
- '//a[contains(., "Paragraph(selected block)")]'
+ await page.waitForSelector(
+ '.block-editor-list-view-block__contents-cell[aria-selected="true"][aria-label^="Paragraph"]'
);
// Go to the image block in list view.
@@ -159,7 +159,9 @@ describe( 'List view', () => {
await pressKeyTimes( 'Enter', 1 );
// Image block should have selected.
- await page.waitForXPath( '//a[contains(., "Image(selected block)")]' );
+ await page.waitForSelector(
+ '.block-editor-list-view-block__contents-cell[aria-selected="true"][aria-label^="Image"]'
+ );
// Image options dropdown.
const imageOptionsButton = await page.waitForSelector(
@@ -176,8 +178,8 @@ describe( 'List view', () => {
await imageRemoveButton.click();
// Heading block should be selected as next block.
- await page.waitForXPath(
- '//a[contains(., "Heading(selected block)")]'
+ await page.waitForSelector(
+ '.block-editor-list-view-block__contents-cell[aria-selected="true"][aria-label^="Heading"]'
);
} );
@@ -194,8 +196,8 @@ describe( 'List view', () => {
await openListView();
// The last inserted heading block should be selected in List View.
- const headingBlock = await page.waitForXPath(
- '//a[contains(., "Heading(selected block)")]'
+ const headingBlock = await page.waitForSelector(
+ '.block-editor-list-view-block__contents-cell[aria-selected="true"][aria-label^="Heading"]'
);
await headingBlock.click();
@@ -204,10 +206,12 @@ describe( 'List view', () => {
await pressKeyWithModifier( 'shift', 'ArrowUp' );
// Both Image and Heading blocks should have selected.
- await page.waitForXPath(
- '//a[contains(., "Heading(selected block)")]'
+ await page.waitForSelector(
+ '.block-editor-list-view-block__contents-cell[aria-selected="true"][aria-label^="Heading"]'
+ );
+ await page.waitForSelector(
+ '.block-editor-list-view-block__contents-cell[aria-selected="true"][aria-label^="Image"]'
);
- await page.waitForXPath( '//a[contains(., "Image(selected block)")]' );
const imageOptionsButton = await page.waitForSelector(
'tr.block-editor-list-view-leaf:first-child button[aria-label="Options for Image block"]'
@@ -224,8 +228,8 @@ describe( 'List view', () => {
await blocksRemoveButton.click();
// Newly created default paragraph block should be selected.
- await page.waitForXPath(
- '//a[contains(., "Paragraph(selected block)")]'
+ await page.waitForSelector(
+ '.block-editor-list-view-block__contents-cell[aria-selected="true"][aria-label^="Paragraph"]'
);
} );
@@ -289,7 +293,9 @@ describe( 'List view', () => {
await pressKeyWithModifier( 'access', 'o' );
// The last inserted group block should be selected in list view.
- await page.waitForXPath( '//a[contains(., "Group(selected block)")]' );
+ await page.waitForSelector(
+ '.block-editor-list-view-block__contents-cell[aria-selected="true"][aria-label^="Group"]'
+ );
// Press Home to go to the first inserted block (image).
await page.keyboard.press( 'Home' );