Skip to content

Commit

Permalink
Try better block labels (#18132)
Browse files Browse the repository at this point in the history
* Extract `getBlockDisplayName from block navigation list component and add to blocks package. Rename to getBlockLabel. Make experimental. Prepend block title to label. Add tests

Use block label as aria-label for block edit wrapper. Allow custom separator

Use block label for movers

Split functionality into two separate functions, one for visual label and one for accessible label

Fix typo

Co-Authored-By: Marco Zehe <MarcoZehe@users.noreply.github.com>

Consolidate block accessibility label to use the same functionality on mobile and web

Add context argument to getLabel and tidy up some naming

Shorten title of Unrecognized Block to just Unrecognized

Code cleanliness

Rename `getLabel` to `label` for consistency withe other block functions

Avoid using RichText api in favour of a simpler way to strip html tags

Move mobile accessibility labels to be cross-platform

Add row to web aria labels

Fix tests

Use position identifiers in localized text

Use spaces not tabs

Revert changes to block mover, instead use block title for a shorter screen reader description

Add support for columns

Debounce update of block label in block list block

* Update expected label in e2e test

* Update test func to be dynamic

* Fix a comment typo that the speech synthesizer stumbled over.

Co-authored-by: Marco Zehe <MarcoZehe@users.noreply.github.com>
  • Loading branch information
2 people authored and draganescu committed Jan 10, 2020
1 parent a6530ad commit a8f43fa
Show file tree
Hide file tree
Showing 23 changed files with 374 additions and 235 deletions.
42 changes: 33 additions & 9 deletions packages/block-editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import {
isReusableBlock,
isUnmodifiedDefaultBlock,
getUnregisteredTypeHandlerName,
__experimentalGetAccessibleBlockLabel as getAccessibleBlockLabel,
} from '@wordpress/blocks';
import { withFilters, Popover } from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
import {
withDispatch,
withSelect,
Expand All @@ -50,6 +50,32 @@ import useMovingAnimation from './moving-animation';
import { ChildToolbar, ChildToolbarSlot } from './block-child-toolbar';
import { Context } from './root-container';

/**
* A debounced version of getAccessibleBlockLabel, avoids unnecessary updates to the aria-label attribute
* when typing in some blocks, like the paragraph.
*
* @param {Object} blockType The block type object representing the block's definition.
* @param {Object} attributes The block's attribute values.
* @param {number} index The index of the block in the block list.
* @param {string} moverDirection A string representing whether the movers are displayed vertically or horizontally.
* @param {number} delay The debounce delay.
*/
const useDebouncedAccessibleBlockLabel = ( blockType, attributes, index, moverDirection, delay ) => {
const [ blockLabel, setBlockLabel ] = useState( '' );

useEffect( () => {
const timeoutId = setTimeout( () => {
setBlockLabel( getAccessibleBlockLabel( blockType, attributes, index + 1, moverDirection ) );
}, delay );

return () => {
clearTimeout( timeoutId );
};
}, [ blockType, attributes, index, moverDirection, delay ] );

return blockLabel;
};

function BlockListBlock( {
mode,
isFocusMode,
Expand All @@ -71,6 +97,7 @@ function BlockListBlock( {
isSelectionEnabled,
className,
name,
index,
isValid,
isLast,
attributes,
Expand Down Expand Up @@ -115,6 +142,9 @@ function BlockListBlock( {

const [ isToolbarForced, setIsToolbarForced ] = useState( false );

const blockType = getBlockType( name );
const blockAriaLabel = useDebouncedAccessibleBlockLabel( blockType, attributes, index, moverDirection, 400 );

// Handing the focus of the block on creation and update

/**
Expand Down Expand Up @@ -255,13 +285,6 @@ function BlockListBlock( {
{ bindGlobal: true, eventName: 'keydown', isDisabled: ! canFocusHiddenToolbar }
);

// Rendering the output
const blockType = getBlockType( name );
// translators: %s: Type of block (i.e. Text, Image etc)
const blockLabel = sprintf( __( 'Block: %s' ), blockType.title );
// The block as rendered in the editor is composed of general block UI
// (mover, toolbar, wrapper) and the display of the block content.

const isUnregisteredBlock = name === getUnregisteredTypeHandlerName();

// If the block is selected and we're typing the block should not appear.
Expand Down Expand Up @@ -382,7 +405,7 @@ function BlockListBlock( {
// Only allow shortcuts when a blocks is selected and not locked.
onKeyDown={ isSelected && ! isLocked ? onKeyDown : undefined }
tabIndex="0"
aria-label={ blockLabel }
aria-label={ blockAriaLabel }
role="group"
{ ...wrapperProps }
style={
Expand Down Expand Up @@ -560,6 +583,7 @@ const applyWithSelect = withSelect(
hasFixedToolbar: hasFixedToolbar && isLargeViewport,
isLast: index === blockOrder.length - 1,
isNavigationMode: isNavigationMode(),
index,
isRTL,

// Users of the editor.BlockListBlock filter used to be able to access the block prop
Expand Down
40 changes: 10 additions & 30 deletions packages/block-editor/src/components/block-list/block.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ import { Component } from '@wordpress/element';
import { ToolbarButton, Toolbar } from '@wordpress/components';
import { withDispatch, withSelect } from '@wordpress/data';
import { compose, withPreferredColorScheme } from '@wordpress/compose';
import { getBlockType, getUnregisteredTypeHandlerName } from '@wordpress/blocks';
import { __, sprintf } from '@wordpress/i18n';
import {
getBlockType,
getUnregisteredTypeHandlerName,
__experimentalGetAccessibleBlockLabel as getAccessibleBlockLabel,
} from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
Expand Down Expand Up @@ -77,31 +81,6 @@ class BlockListBlock extends Component {
);
}

getAccessibilityLabel() {
const { attributes, name, order, title, getAccessibilityLabelExtra } = this.props;

let blockName = '';

if ( name === 'core/missing' ) { // is the block unrecognized?
blockName = title;
} else {
blockName = sprintf(
/* translators: accessibility text. %s: block name. */
__( '%s Block' ),
title, //already localized
);
}

blockName += '. ' + sprintf( __( 'Row %d.' ), order + 1 );

if ( getAccessibilityLabelExtra ) {
const blockAccessibilityLabel = getAccessibilityLabelExtra( attributes );
blockName += blockAccessibilityLabel ? ' ' + blockAccessibilityLabel : '';
}

return blockName;
}

applySelectedBlockStyle() {
const {
hasChildren,
Expand Down Expand Up @@ -201,17 +180,20 @@ class BlockListBlock extends Component {

render() {
const {
attributes,
blockType,
clientId,
icon,
isSelected,
isValid,
order,
title,
showFloatingToolbar,
parentId,
isTouchable,
} = this.props;

const accessibilityLabel = this.getAccessibilityLabel();
const accessibilityLabel = getAccessibleBlockLabel( blockType, attributes, order + 1 );

return (
<>
Expand Down Expand Up @@ -277,7 +259,6 @@ export default compose( [
const blockType = getBlockType( name || 'core/missing' );
const title = blockType.title;
const icon = blockType.icon;
const getAccessibilityLabelExtra = blockType.__experimentalGetAccessibilityLabel;

const parents = getBlockParents( clientId, true );
const parentId = parents[ 0 ] || '';
Expand Down Expand Up @@ -319,7 +300,6 @@ export default compose( [
isLastBlock,
isSelected,
isValid,
getAccessibilityLabelExtra,
showFloatingToolbar,
parentId,
isParentSelected,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,113 +8,113 @@ describe( 'block mover', () => {
positiveDirection = 1;

describe( 'getBlockMoverDescription', () => {
const type = 'TestType';
const label = 'Header: Some Header Text';

it( 'generates a title for the first item moving up', () => {
expect( getBlockMoverDescription(
1,
type,
label,
0,
true,
false,
negativeDirection
) ).toBe(
`Block ${ type } is at the beginning of the content and can’t be moved up`
`Block ${ label } is at the beginning of the content and can’t be moved up`
);
} );

it( 'generates a title for the last item moving down', () => {
expect( getBlockMoverDescription(
1,
type,
label,
3,
false,
true,
positiveDirection
) ).toBe( `Block ${ type } is at the end of the content and can’t be moved down` );
positiveDirection,
) ).toBe( `Block ${ label } is at the end of the content and can’t be moved down` );
} );

it( 'generates a title for the second item moving up', () => {
expect( getBlockMoverDescription(
1,
type,
label,
1,
false,
false,
negativeDirection
) ).toBe( `Move ${ type } block from position 2 up to position 1` );
negativeDirection,
) ).toBe( `Move ${ label } block from position 2 up to position 1` );
} );

it( 'generates a title for the second item moving down', () => {
expect( getBlockMoverDescription(
1,
type,
label,
1,
false,
false,
positiveDirection
) ).toBe( `Move ${ type } block from position 2 down to position 3` );
positiveDirection,
) ).toBe( `Move ${ label } block from position 2 down to position 3` );
} );

it( 'generates a title for the only item in the list', () => {
expect( getBlockMoverDescription(
1,
type,
label,
0,
true,
true,
positiveDirection
) ).toBe( `Block ${ type } is the only block, and cannot be moved` );
positiveDirection,
) ).toBe( `Block ${ label } is the only block, and cannot be moved` );
} );

it( 'indicates that the block can be moved left when the orientation is horizontal and the direction is negative', () => {
expect( getBlockMoverDescription(
1,
type,
label,
1,
false,
false,
negativeDirection,
'horizontal'
) ).toBe( `Move ${ type } block from position 2 left to position 1` );
) ).toBe( `Move ${ label } block from position 2 left to position 1` );
} );

it( 'indicates that the block can be moved right when the orientation is horizontal and the direction is positive', () => {
expect( getBlockMoverDescription(
1,
type,
label,
1,
false,
false,
positiveDirection,
'horizontal'
) ).toBe( `Move ${ type } block from position 2 right to position 3` );
) ).toBe( `Move ${ label } block from position 2 right to position 3` );
} );

it( 'indicates that the block cannot be moved left when the orientation is horizontal and the block is the first block', () => {
expect( getBlockMoverDescription(
1,
type,
label,
0,
true,
false,
negativeDirection,
'horizontal'
) ).toBe(
`Block ${ type } is at the beginning of the content and can’t be moved left`
`Block ${ label } is at the beginning of the content and can’t be moved left`
);
} );

it( 'indicates that the block cannot be moved right when the orientation is horizontal and the block is the last block', () => {
expect( getBlockMoverDescription(
1,
type,
label,
3,
false,
true,
positiveDirection,
'horizontal'
) ).toBe( `Block ${ type } is at the end of the content and can’t be moved right` );
) ).toBe( `Block ${ label } is at the end of the content and can’t be moved right` );
} );
} );

Expand Down
30 changes: 5 additions & 25 deletions packages/block-editor/src/components/block-navigation/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,18 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { Button } from '@wordpress/components';
import { getBlockType } from '@wordpress/blocks';
import {
__experimentalGetBlockLabel as getBlockLabel,
getBlockType,
} from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import { create, getTextContent } from '@wordpress/rich-text';

/**
* Internal dependencies
*/
import BlockIcon from '../block-icon';
import ButtonBlockAppender from '../button-block-appender';

/**
* Get the block display name, if it has one, or the block title if it doesn't.
*
* @param {Object} blockType The block type.
* @param {Object} attributes The values of the block's attributes
*
* @return {string} The display name value.
*/
function getBlockDisplayName( blockType, attributes ) {
const displayNameAttribute = blockType.__experimentalDisplayName;

if ( ! displayNameAttribute || ! attributes[ displayNameAttribute ] ) {
return blockType.title;
}

// Strip any formatting.
const richTextValue = create( { html: attributes[ displayNameAttribute ] } );
const formatlessDisplayName = getTextContent( richTextValue );

return formatlessDisplayName;
}

export default function BlockNavigationList( {
blocks,
selectedBlockClientId,
Expand Down Expand Up @@ -73,7 +53,7 @@ export default function BlockNavigationList( {
onClick={ () => selectBlock( block.clientId ) }
>
<BlockIcon icon={ blockType.icon } showColors />
{ getBlockDisplayName( blockType, block.attributes ) }
{ getBlockLabel( blockType, block.attributes ) }
{ isSelected && <span className="screen-reader-text">{ __( '(selected block)' ) }</span> }
</Button>
</div>
Expand Down
Loading

0 comments on commit a8f43fa

Please sign in to comment.