From fedaf189c3661c12da8fd7530f6bdcd3c698ad5b Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 8 Mar 2022 12:06:25 +1100 Subject: [PATCH 1/8] List View: Add multi-select support for shift + Home and End keys --- .../src/components/list-view/use-block-selection.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/list-view/use-block-selection.js b/packages/block-editor/src/components/list-view/use-block-selection.js index b6ac6e1825d417..2014189bf1154f 100644 --- a/packages/block-editor/src/components/list-view/use-block-selection.js +++ b/packages/block-editor/src/components/list-view/use-block-selection.js @@ -10,7 +10,7 @@ import { speak } from '@wordpress/a11y'; import { __, sprintf } from '@wordpress/i18n'; import { useDispatch, useSelect } from '@wordpress/data'; import { useCallback } from '@wordpress/element'; -import { UP, DOWN } from '@wordpress/keycodes'; +import { UP, DOWN, HOME, END } from '@wordpress/keycodes'; import { store as blocksStore } from '@wordpress/blocks'; /** @@ -49,7 +49,10 @@ export default function useBlockSelection() { const isKeyPress = event.type === 'keydown' && - ( event.keyCode === UP || event.keyCode === DOWN ); + ( event.keyCode === UP || + event.keyCode === DOWN || + event.keyCode === HOME || + event.keyCode === END ); // Handle clicking on a block when no blocks are selected, and return early. if ( From 058b8a6b9335921bccae67031ba2034d6c976adf Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 8 Mar 2022 15:57:57 +1100 Subject: [PATCH 2/8] Skip announcing deselected blocks when using Home and End keys for the selection --- .../src/components/list-view/use-block-selection.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/block-editor/src/components/list-view/use-block-selection.js b/packages/block-editor/src/components/list-view/use-block-selection.js index 2014189bf1154f..2edcd3ed8bad3d 100644 --- a/packages/block-editor/src/components/list-view/use-block-selection.js +++ b/packages/block-editor/src/components/list-view/use-block-selection.js @@ -117,6 +117,16 @@ export default function useBlockSelection() { // the total number of blocks deselected is greater than one. const updatedSelectedBlocks = getSelectedBlockClientIds(); + // If the selection is greater than 1 and the Home or End keys + // were used to generate the selection, then skip announcing the + // deselected blocks. + if ( + ( event.keyCode === HOME || event.keyCode === END ) && + updatedSelectedBlocks.length > 1 + ) { + return; + } + const selectionDiff = difference( selectedBlocks, updatedSelectedBlocks From 2692f44b64f4fe9ff966feb4f9d1e0bf4390df29 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 18 Mar 2022 16:08:45 +1100 Subject: [PATCH 3/8] Try setting preventAnnouncement at the parent list view, and pass down to the select button --- .../components/list-view/block-contents.js | 2 ++ .../list-view/block-select-button.js | 26 +++++++++++++++++-- .../src/components/list-view/block.js | 2 ++ .../src/components/list-view/branch.js | 2 ++ .../src/components/list-view/index.js | 10 +++++++ 5 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block-contents.js b/packages/block-editor/src/components/list-view/block-contents.js index 2fb787dac3137e..d6fdfcdb0644a8 100644 --- a/packages/block-editor/src/components/list-view/block-contents.js +++ b/packages/block-editor/src/components/list-view/block-contents.js @@ -28,6 +28,7 @@ const ListViewBlockContents = forwardRef( level, isExpanded, selectedClientIds, + preventAnnouncement, ...props }, ref @@ -80,6 +81,7 @@ const ListViewBlockContents = forwardRef( onDragStart={ onDragStart } onDragEnd={ onDragEnd } isExpanded={ isExpanded } + preventAnnouncement={ preventAnnouncement } { ...props } /> ) } 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 452f01d30b3bd4..2c44cbbc995c82 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 @@ -7,8 +7,8 @@ import classnames from 'classnames'; * WordPress dependencies */ import { Button, VisuallyHidden } from '@wordpress/components'; -import { useInstanceId } from '@wordpress/compose'; -import { forwardRef } from '@wordpress/element'; +import { useDebounce, useInstanceId, usePrevious } from '@wordpress/compose'; +import { forwardRef, useEffect, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -37,6 +37,7 @@ function ListViewBlockSelectButton( onDragEnd, draggable, isExpanded, + preventAnnouncement, }, ref ) { @@ -48,6 +49,11 @@ function ListViewBlockSelectButton( siblingBlockCount, level ); + const [ ariaHidden, setAriaHidden ] = useState( undefined ); + + // This debounced version is used so that while moving out of focus, + // the block isn't updated and then re-announced. + const delaySetAriaHidden = useDebounce( setAriaHidden, 200 ); // 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. @@ -64,6 +70,21 @@ function ListViewBlockSelectButton( } } + const previousPreventAnnouncement = usePrevious( preventAnnouncement ); + + useEffect( () => { + // If we prevent screen readers from announcing the block, + // we should apply this immediately. + if ( preventAnnouncement ) { + setAriaHidden( true ); + } + // Delay re-enabling so that if focus is being moved between + // buttons, we don't accidentally re-announce a focused button. + if ( ! preventAnnouncement && previousPreventAnnouncement ) { + delaySetAriaHidden( undefined ); + } + }, [ preventAnnouncement ] ); + return ( <> -
- { blockPositionDescription } -
); } diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index bc8764c0ab2c00..ea16ddcd9de564 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'; @@ -57,6 +59,23 @@ 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 settingsAriaLabel = blockInformation + ? sprintf( + // translators: %s: The title of the block. + __( 'Options for %s block' ), + blockInformation.title + ) + : __( 'Options' ); + const { __experimentalFeatures: withExperimentalFeatures, __experimentalPersistentListViewFeatures: withExperimentalPersistentListViewFeatures, @@ -156,15 +175,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 @@ -187,11 +197,16 @@ function ListViewBlock( { id={ `list-view-block-${ clientId }` } data-block={ clientId } isExpanded={ isExpanded } + aria-selected={ !! isSelected } > { ( { ref, tabIndex, onFocus } ) => (
@@ -210,6 +225,12 @@ function ListViewBlock( { selectedClientIds={ selectedClientIds } preventAnnouncement={ preventAnnouncement } /> +
+ { blockPositionDescription } +
) }
@@ -246,7 +267,10 @@ function ListViewBlock( { ) } { showBlockActions && ( - + { ( { ref, tabIndex, onFocus } ) => ( ) } { ! showBlock && ( diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index 35b059e74f096b..ab1c8051923c83 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -13,10 +13,8 @@ import { useMemo, useRef, useReducer, - useState, forwardRef, } from '@wordpress/element'; -import { HOME, END } from '@wordpress/keycodes'; import { __ } from '@wordpress/i18n'; /** @@ -104,7 +102,6 @@ function ListView( const { updateBlockSelection } = useBlockSelection(); const [ expandedState, setExpandedState ] = useReducer( expanded, {} ); - const [ preventAnnouncement, setPreventAnnouncement ] = useState( false ); const { ref: dropZoneRef, target: blockDropTarget } = useListViewDropZone(); const elementRef = useRef(); @@ -119,7 +116,6 @@ function ListView( ( event, clientId ) => { updateBlockSelection( event, clientId ); setSelectedTreeId( clientId ); - setPreventAnnouncement( false ); }, [ setSelectedTreeId, updateBlockSelection ] ); @@ -178,11 +174,6 @@ function ListView( startRow?.dataset?.block, endRow?.dataset?.block ); - if ( event.keyCode === HOME || event.keyCode === END ) { - setPreventAnnouncement( true ); - } - } else { - setPreventAnnouncement( false ); } }, [ updateBlockSelection ] @@ -235,7 +226,6 @@ function ListView( fixedListWindow={ fixedListWindow } selectedClientIds={ selectedClientIds } expandNested={ expandNested } - preventAnnouncement={ preventAnnouncement } { ...props } /> From a8219d34249bfe260b9e2238ce05af90d956ecf7 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 22 Mar 2022 15:26:56 +1100 Subject: [PATCH 5/8] Add link to block title label, restore aria-expanded at the button level --- .../src/components/list-view/block-select-button.js | 2 ++ .../block-editor/src/components/list-view/block.js | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) 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 ad6a05818afdaf..f40d68e6b882e1 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 @@ -29,6 +29,7 @@ function ListViewBlockSelectButton( onDragStart, onDragEnd, draggable, + isExpanded, }, ref ) { @@ -65,6 +66,7 @@ function ListViewBlockSelectButton( onDragEnd={ onDragEnd } draggable={ draggable } href={ `#block-${ clientId }` } + aria-expanded={ isExpanded } aria-hidden={ true } > diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index ea16ddcd9de564..71a1c42638157d 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -68,6 +68,14 @@ function ListViewBlock( { 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. @@ -203,7 +211,7 @@ function ListViewBlock( { className="block-editor-list-view-block__contents-cell" colSpan={ colSpan } ref={ cellRef } - aria-label={ blockInformation.title } + aria-label={ blockAriaLabel } aria-selected={ !! isSelected } aria-expanded={ isExpanded } aria-describedby={ descriptionId } From 39f6a76f3155ea0318762c1dce12556ccc630aec Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 22 Mar 2022 16:56:24 +1100 Subject: [PATCH 6/8] Update e2e tests to look for aria-selected attributes --- .../specs/editor/various/list-view.test.js | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) 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 7ec97b9a142315..072f549214f49b 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' ); From 4dc20b9aa03ecdef9e638b58c3bbf4c14494335a Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 22 Mar 2022 17:11:37 +1100 Subject: [PATCH 7/8] Try removing code that prevents the announcement of deselected blocks --- .../src/components/list-view/use-block-selection.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/block-editor/src/components/list-view/use-block-selection.js b/packages/block-editor/src/components/list-view/use-block-selection.js index 2edcd3ed8bad3d..2014189bf1154f 100644 --- a/packages/block-editor/src/components/list-view/use-block-selection.js +++ b/packages/block-editor/src/components/list-view/use-block-selection.js @@ -117,16 +117,6 @@ export default function useBlockSelection() { // the total number of blocks deselected is greater than one. const updatedSelectedBlocks = getSelectedBlockClientIds(); - // If the selection is greater than 1 and the Home or End keys - // were used to generate the selection, then skip announcing the - // deselected blocks. - if ( - ( event.keyCode === HOME || event.keyCode === END ) && - updatedSelectedBlocks.length > 1 - ) { - return; - } - const selectionDiff = difference( selectedBlocks, updatedSelectedBlocks From ad382ac7a481922d92f23ba556ce971393e3bcc8 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 23 Mar 2022 12:45:00 +1100 Subject: [PATCH 8/8] Add back in skipping announcing deselected blocks --- .../src/components/list-view/block-select-button.js | 2 -- .../src/components/list-view/use-block-selection.js | 10 ++++++++++ 2 files changed, 10 insertions(+), 2 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 f40d68e6b882e1..ad6a05818afdaf 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 @@ -29,7 +29,6 @@ function ListViewBlockSelectButton( onDragStart, onDragEnd, draggable, - isExpanded, }, ref ) { @@ -66,7 +65,6 @@ function ListViewBlockSelectButton( onDragEnd={ onDragEnd } draggable={ draggable } href={ `#block-${ clientId }` } - aria-expanded={ isExpanded } aria-hidden={ true } > diff --git a/packages/block-editor/src/components/list-view/use-block-selection.js b/packages/block-editor/src/components/list-view/use-block-selection.js index 2014189bf1154f..2edcd3ed8bad3d 100644 --- a/packages/block-editor/src/components/list-view/use-block-selection.js +++ b/packages/block-editor/src/components/list-view/use-block-selection.js @@ -117,6 +117,16 @@ export default function useBlockSelection() { // the total number of blocks deselected is greater than one. const updatedSelectedBlocks = getSelectedBlockClientIds(); + // If the selection is greater than 1 and the Home or End keys + // were used to generate the selection, then skip announcing the + // deselected blocks. + if ( + ( event.keyCode === HOME || event.keyCode === END ) && + updatedSelectedBlocks.length > 1 + ) { + return; + } + const selectionDiff = difference( selectedBlocks, updatedSelectedBlocks