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' );