From ea11eb9b06e218f7f77996eed8d24c8a03f152ae Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Thu, 6 Feb 2020 11:31:00 +0800 Subject: [PATCH] Split block label and accessibility label --- .../developers/data/data-core-block-editor.md | 28 ++++ .../components/block-list/block-popover.js | 2 - .../src/components/block-list/block.native.js | 20 +-- .../src/components/block-list/breadcrumb.js | 37 +---- .../src/components/block-navigation/item.js | 60 ++++++++ .../src/components/block-navigation/list.js | 56 ++----- .../components/block-navigation/style.scss | 7 +- packages/block-editor/src/store/selectors.js | 127 ++++++++++++++++ .../block-editor/src/store/test/selectors.js | 142 ++++++++++++++++++ packages/block-library/src/heading/index.js | 30 ++-- packages/block-library/src/image/index.js | 24 ++- packages/block-library/src/missing/index.js | 20 +-- packages/block-library/src/more/index.js | 6 +- .../src/navigation-link/index.js | 6 +- packages/block-library/src/paragraph/index.js | 7 +- packages/blocks/src/api/index.js | 2 - packages/blocks/src/api/test/utils.js | 111 +------------- packages/blocks/src/api/utils.js | 106 ------------- packages/dom/src/dom.js | 14 +- packages/dom/src/test/dom.js | 15 -- .../specs/editor/blocks/columns.test.js | 2 +- .../specs/editor/plugins/block-icons.test.js | 2 +- .../block-hierarchy-navigation.test.js | 6 +- packages/rich-text/README.md | 12 ++ packages/rich-text/src/index.js | 1 + packages/rich-text/src/test/to-plain-text.js | 27 ++++ packages/rich-text/src/to-plain-text.js | 19 +++ 27 files changed, 490 insertions(+), 399 deletions(-) create mode 100644 packages/block-editor/src/components/block-navigation/item.js create mode 100644 packages/rich-text/src/test/to-plain-text.js create mode 100644 packages/rich-text/src/to-plain-text.js diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index 588b1f27e32499..e69090f3c6503f 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -32,6 +32,20 @@ _Returns_ - `boolean`: Whether the last change was automatic. +# **getAccessibleBlockLabel** + +Get a label for the block for use by screenreaders, this can include the block title, the +position of the block, and the value of the `getAccessibilityLabel` function if it's specified. + +_Parameters_ + +- _state_ `Object`: Store state. +- _clientId_ `string`: ClientId for the block. + +_Returns_ + +- `string`: The accessibility label for the block. + # **getAdjacentBlockClientId** Returns the client ID of the block adjacent one at the given reference @@ -133,6 +147,20 @@ _Returns_ - `Object`: Insertion point object with `rootClientId`, `index`. +# **getBlockLabel** + +Get the label for the block, usually this is either the block title, +or the value of the attribute denoted by the block's `label` property. + +_Parameters_ + +- _state_ `Object`: Store state. +- _clientId_ `string`: ClientId for the block. + +_Returns_ + +- `string`: The block label. + # **getBlockListSettings** Returns the Block List settings of a block, if any exist. diff --git a/packages/block-editor/src/components/block-list/block-popover.js b/packages/block-editor/src/components/block-list/block-popover.js index 2a70b80c2dafa6..9be114b7a0ff78 100644 --- a/packages/block-editor/src/components/block-list/block-popover.js +++ b/packages/block-editor/src/components/block-list/block-popover.js @@ -206,8 +206,6 @@ function BlockPopover( { { shouldShowBreadcrumb && ( ) } diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 6a165a59aee5d8..755499674abc23 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -13,7 +13,6 @@ import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { getBlockType, getUnregisteredTypeHandlerName, - __experimentalGetAccessibleBlockLabel as getAccessibleBlockLabel, } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; @@ -194,25 +193,17 @@ class BlockListBlock extends Component { render() { const { - attributes, - blockType, + accessibilityLabel, clientId, icon, isSelected, isValid, - order, title, showFloatingToolbar, parentId, isTouchable, } = this.props; - const accessibilityLabel = getAccessibleBlockLabel( - blockType, - attributes, - order + 1 - ); - return ( <> { showFloatingToolbar && ( @@ -263,6 +254,7 @@ class BlockListBlock extends Component { export default compose( [ withSelect( ( select, { clientId, rootClientId } ) => { const { + getAccessibleBlockLabel, getBlockIndex, isBlockSelected, __unstableGetBlockWithoutInnerBlocks, @@ -281,7 +273,7 @@ export default compose( [ const isSelected = isBlockSelected( clientId ); const isLastBlock = order === getBlockCount( rootClientId ) - 1; const block = __unstableGetBlockWithoutInnerBlocks( clientId ); - const { name, attributes, isValid } = block || {}; + const { name, isValid } = block || {}; const isUnregisteredBlock = name === getUnregisteredTypeHandlerName(); const blockType = getBlockType( name || 'core/missing' ); @@ -341,13 +333,13 @@ export default compose( [ const isRootListInnerBlockHolder = ! isSelectedBlockNested && isInnerBlockHolder; + const accessibilityLabel = getAccessibleBlockLabel( clientId ); + return { icon, name: name || 'core/missing', - order, + accessibilityLabel, title, - attributes, - blockType, isLastBlock, isSelected, isValid, diff --git a/packages/block-editor/src/components/block-list/breadcrumb.js b/packages/block-editor/src/components/block-list/breadcrumb.js index a47db75152663e..5c2af840e9653b 100644 --- a/packages/block-editor/src/components/block-list/breadcrumb.js +++ b/packages/block-editor/src/components/block-list/breadcrumb.js @@ -5,10 +5,6 @@ import { Toolbar, Button } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { useEffect, useRef } from '@wordpress/element'; import { BACKSPACE, DELETE } from '@wordpress/keycodes'; -import { - getBlockType, - __experimentalGetAccessibleBlockLabel as getAccessibleBlockLabel, -} from '@wordpress/blocks'; /** * Internal dependencies @@ -25,27 +21,12 @@ import BlockTitle from '../block-title'; * * @return {WPComponent} The component to be rendered. */ -function BlockBreadcrumb( { - clientId, - rootClientId, - moverDirection, - ...props -} ) { - const selected = useSelect( - ( select ) => { - const { - __unstableGetBlockWithoutInnerBlocks, - getBlockIndex, - } = select( 'core/block-editor' ); - const index = getBlockIndex( clientId, rootClientId ); - const { name, attributes } = __unstableGetBlockWithoutInnerBlocks( - clientId - ); - return { index, name, attributes }; - }, - [ clientId, rootClientId ] +function BlockBreadcrumb( { clientId, ...props } ) { + const label = useSelect( + ( select ) => + select( 'core/block-editor' ).getAccessibleBlockLabel( clientId ), + [ clientId ] ); - const { index, name, attributes } = selected; const { setNavigationMode, removeBlock } = useDispatch( 'core/block-editor' ); @@ -65,14 +46,6 @@ function BlockBreadcrumb( { } } - const blockType = getBlockType( name ); - const label = getAccessibleBlockLabel( - blockType, - attributes, - index + 1, - moverDirection - ); - return (
diff --git a/packages/block-editor/src/components/block-navigation/item.js b/packages/block-editor/src/components/block-navigation/item.js new file mode 100644 index 00000000000000..c205f2d1171c69 --- /dev/null +++ b/packages/block-editor/src/components/block-navigation/item.js @@ -0,0 +1,60 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; +import { + getBlockType, +} from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import BlockIcon from '../block-icon'; +import ButtonBlockAppender from '../button-block-appender'; + +export default function BlockNavigationItem( { block, isSelected, onClick, children } ) { + const { clientId, name } = block; + const blockIcon = getBlockType( name ).icon; + const blockLabel = useSelect( + ( select ) => select( 'core/block-editor' ).getBlockLabel( clientId ), + [ clientId ] + ); + + return ( +
  • +
    + +
    + { children } +
  • + ); +} + +BlockNavigationItem.Appender = function( { parentBlockClientId } ) { + return ( +
  • +
    + +
    +
  • + ); +}; diff --git a/packages/block-editor/src/components/block-navigation/list.js b/packages/block-editor/src/components/block-navigation/list.js index 32c8eba5049e89..da0e2c6a338e47 100644 --- a/packages/block-editor/src/components/block-navigation/list.js +++ b/packages/block-editor/src/components/block-navigation/list.js @@ -2,23 +2,11 @@ * External dependencies */ import { isNil, map, omitBy } from 'lodash'; -import classnames from 'classnames'; - -/** - * WordPress dependencies - */ -import { Button } from '@wordpress/components'; -import { - __experimentalGetBlockLabel as getBlockLabel, - getBlockType, -} from '@wordpress/blocks'; -import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import BlockIcon from '../block-icon'; -import ButtonBlockAppender from '../button-block-appender'; +import BlockNavigationItem from './item'; export default function BlockNavigationList( { blocks, @@ -40,30 +28,13 @@ export default function BlockNavigationList( { /* eslint-disable jsx-a11y/no-redundant-roles */
      { map( omitBy( blocks, isNil ), ( block ) => { - const blockType = getBlockType( block.name ); - const isSelected = block.clientId === selectedBlockClientId; - return ( -
    • -
      - -
      + selectBlock( block.clientId ) } + isSelected={ block.clientId === selectedBlockClientId } + > { showNestedBlocks && !! block.innerBlocks && !! block.innerBlocks.length && ( @@ -78,18 +49,13 @@ export default function BlockNavigationList( { showNestedBlocks /> ) } -
    • + ); } ) } { shouldShowAppender && ( -
    • -
      - -
      -
    • + ) }
    /* eslint-enable jsx-a11y/no-redundant-roles */ diff --git a/packages/block-editor/src/components/block-navigation/style.scss b/packages/block-editor/src/components/block-navigation/style.scss index 737353fedf67bc..21eb94d9cab0f6 100644 --- a/packages/block-editor/src/components/block-navigation/style.scss +++ b/packages/block-editor/src/components/block-navigation/style.scss @@ -43,7 +43,8 @@ $tree-item-height: 36px; margin-left: 1.5em; } - .block-editor-block-navigation__item { + .block-editor-block-navigation-item__block, + .block-editor-block-navigation-item__appender { position: relative; &::before { @@ -57,7 +58,7 @@ $tree-item-height: 36px; } } - .block-editor-block-navigation__item-button { + .block-editor-block-navigation-item__button { margin-left: 0.8em; width: calc(100% - 0.8em); } @@ -76,7 +77,7 @@ $tree-item-height: 36px; } } -.block-editor-block-navigation__item-button { +.block-editor-block-navigation-item__button { display: flex; align-items: center; width: 100%; diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 9d71c1c29c3fd2..3ac773fae0d01b 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -29,6 +29,8 @@ import { parse, } from '@wordpress/blocks'; import { SVG, Rect, G, Path } from '@wordpress/components'; +import { toPlainText } from '@wordpress/rich-text'; +import { __, sprintf } from '@wordpress/i18n'; /** * A block selection object. @@ -1623,3 +1625,128 @@ export function isNavigationMode( state ) { export function didAutomaticChange( state ) { return !! state.automaticChangeStatus; } + +/** + * Get the label for the block, usually this is either the block title, + * or the value of the attribute denoted by the block's `label` property. + * + * @param {Object} state Store state. + * @param {string} clientId ClientId for the block. + * + * @return {string} The block label. + */ +export function getBlockLabel( state, clientId ) { + const blockName = getBlockName( state, clientId ); + + const { __experimentalLabel: labelAttribute, title } = getBlockType( + blockName + ); + + if ( ! labelAttribute ) { + return title; + } + + const attributes = getBlockAttributes( state, clientId ); + const label = attributes[ labelAttribute ]; + + if ( ! label ) { + return title; + } + + // Strip any HTML (i.e. RichText formatting) before returning. + return toPlainText( label ); +} + +/** + * Get a label for the block for use by screenreaders, this can include the block title, the + * position of the block, and the value of the `getAccessibilityLabel` function if it's specified. + * + * @param {Object} state Store state. + * @param {string} clientId ClientId for the block. + * + * @return {string} The accessibility label for the block. + */ +export function getAccessibleBlockLabel( state, clientId ) { + const blockName = getBlockName( state, clientId ); + + const { + __experimentalGetAccessibilityLabel: getAccessibilityLabel, + title, + } = getBlockType( blockName ); + + const rootClientId = getBlockRootClientId( state, clientId ); + const attributes = getBlockAttributes( state, clientId ); + const { __experimentalMoverDirection: direction = 'vertical' } = + getBlockListSettings( state, rootClientId ) || {}; + + // First, attempt to get the accessibility label if the block has one defined. + let label = getAccessibilityLabel + ? toPlainText( getAccessibilityLabel( attributes ) ) + : undefined; + + // If there's no accessibility label, use the block label. + if ( ! label ) { + label = getBlockLabel( state, clientId ); + } + + const position = getBlockIndex( state, clientId, rootClientId ) + 1; + + // getBlockLabel returns the block title as a fallback when there's no label, + // if it did return the title, this function needs to avoid adding the + // title twice within the accessible label. Use this `hasLabel` boolean to + // handle that. + const hasLabel = !! label && label !== title; + + const hasPosition = position !== undefined; + + if ( hasPosition && direction === 'vertical' ) { + if ( hasLabel ) { + return sprintf( + /* translators: accessibility text. %1: The block title, %2: The block row number, %3: The block label.. */ + __( '%1$s Block. Row %2$d. %3$s' ), + title, + position, + label + ); + } + + return sprintf( + /* translators: accessibility text. %s: The block title, %d The block row number. */ + __( '%s Block. Row %d' ), + title, + position + ); + } else if ( hasPosition && direction === 'horizontal' ) { + if ( hasLabel ) { + return sprintf( + /* translators: accessibility text. %1: The block title, %2: The block column number, %3: The block label.. */ + __( '%1$s Block. Column %2$d. %3$s' ), + title, + position, + label + ); + } + + return sprintf( + /* translators: accessibility text. %s: The block title, %d The block column number. */ + __( '%s Block. Column %d' ), + title, + position + ); + } + + if ( hasLabel ) { + return sprintf( + /* translators: accessibility text. %1: The block title. %2: The block label. */ + __( '%1$s Block. %2$s' ), + title, + label + ); + } + + return sprintf( + /* translators: accessibility text. %s: The block title. */ + __( '%s Block' ), + title + ); +} diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index d6f4cfaac20e17..3efb9c8062da07 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -64,6 +64,8 @@ const { INSERTER_UTILITY_MEDIUM, INSERTER_UTILITY_LOW, getLowestCommonAncestorWithSelectedBlock, + getBlockLabel, + getAccessibleBlockLabel, } = selectors; describe( 'selectors', () => { @@ -100,6 +102,10 @@ describe( 'selectors', () => { supports: { multiple: false, }, + __experimentalLabel: 'title', + __experimentalGetAccessibilityLabel( { content } ) { + return content; + }, } ); registerBlockType( 'core/test-block-c', { @@ -2679,4 +2685,140 @@ describe( 'selectors', () => { ).toBe( 'a' ); } ); } ); + + describe( 'getLabel', () => { + const state = { + blocks: { + byClientId: { + 123: { clientId: 123, name: 'core/test-block-a' }, + 456: { clientId: 456, name: 'core/test-block-b' }, + }, + attributes: { + 123: {}, + 456: { title: 'A title', content: 'Some content' }, + }, + }, + }; + + it( 'returns only the block title when the block has no `label` attribute', () => { + expect( getBlockLabel( state, 123 ) ).toBe( 'Test Block A' ); + } ); + + it( 'returns the label when the `label` attribute is defined', () => { + expect( getBlockLabel( state, 456 ) ).toBe( 'A title' ); + } ); + + it( 'returns the block title when the label attribute is defined but does not match an attribute', () => { + const stateWithEmptyAttributes = { + blocks: { + ...state.blocks, + attributes: { + ...state.blocks.attributes, + 456: {}, + }, + }, + }; + expect( getBlockLabel( stateWithEmptyAttributes, 456 ) ).toBe( 'Test Block B' ); + } ); + + it( 'removes any html elements from the attribute', () => { + const stateWithHTMLContent = { + blocks: { + ...state.blocks, + attributes: { + ...state.blocks.attributes, + 456: { title: 'A title' }, + }, + }, + }; + expect( getBlockLabel( stateWithHTMLContent, 456 ) ).toBe( 'A title' ); + } ); + } ); + + describe( 'getAccessibleBlockLabel', () => { + const state = { + blocks: { + byClientId: { + 123: { clientId: 123, name: 'core/test-block-a' }, + 456: { clientId: 456, name: 'core/test-block-b' }, + }, + attributes: { + 123: {}, + 456: { title: 'A title', content: 'Some content' }, + }, + order: { + '': [ 123, 456 ], + }, + parents: { + 123: '', + 456: '', + }, + }, + blockListSettings: { + '': {}, + }, + }; + + it( 'returns the block title and row when the block has no `getAccessibilityLabel` function', () => { + expect( getAccessibleBlockLabel( state, 123 ) ).toBe( 'Test Block A Block. Row 1' ); + } ); + + it( 'returns the block title with the row and label when the `getAccessibilityLabel` function returns a value', () => { + expect( getAccessibleBlockLabel( state, 456 ) ).toBe( 'Test Block B Block. Row 2. Some content' ); + } ); + + it( 'returns column instead of row when the direction is horizontal', () => { + const stateWithHorizontalBlockList = { + ...state, + blockListSettings: { + '': { + __experimentalMoverDirection: 'horizontal', + }, + }, + }; + expect( getAccessibleBlockLabel( stateWithHorizontalBlockList, 456 ) ).toBe( 'Test Block B Block. Column 2. Some content' ); + } ); + + it( 'returns the block title and row when attributes are undefined', () => { + const stateWithEmptyAttributes = { + ...state, + blocks: { + ...state.blocks, + attributes: { + ...state.blocks.attributes, + 456: {}, + }, + }, + }; + expect( getAccessibleBlockLabel( stateWithEmptyAttributes, 456 ) ).toBe( 'Test Block B Block. Row 2' ); + } ); + + it( 'falls back to using the `label` when the accessibility label is undefined', () => { + const stateWithMissingAttribute = { + ...state, + blocks: { + ...state.blocks, + attributes: { + ...state.blocks.attributes, + 456: { title: 'A title' }, + }, + }, + }; + expect( getAccessibleBlockLabel( stateWithMissingAttribute, 456 ) ).toBe( 'Test Block B Block. Row 2. A title' ); + } ); + + it( 'removes any html elements from the output of the `getLabel` function', () => { + const stateWithHTMLContent = { + ...state, + blocks: { + ...state.blocks, + attributes: { + ...state.blocks.attributes, + 456: { content: 'Some content' }, + }, + }, + }; + expect( getAccessibleBlockLabel( stateWithHTMLContent, 456 ) ).toBe( 'Test Block B Block. Row 2. Some content' ); + } ); + } ); } ); diff --git a/packages/block-library/src/heading/index.js b/packages/block-library/src/heading/index.js index c5b3cd4e8a9c1d..a4837676d0ccaf 100644 --- a/packages/block-library/src/heading/index.js +++ b/packages/block-library/src/heading/index.js @@ -40,23 +40,21 @@ export const settings = { level: 2, }, }, - __experimentalLabel( attributes, { context } ) { - if ( context === 'accessibility' ) { - const { content, level } = attributes; - - return isEmpty( content ) - ? sprintf( - /* translators: accessibility text. %s: heading level. */ - __( 'Level %s. Empty.' ), - level - ) - : sprintf( - /* translators: accessibility text. 1: heading level. 2: heading content. */ - __( 'Level %1$s. %2$s' ), - level, - content - ); + __experimentalGetAccessibilityLabel( { content, level } ) { + if ( isEmpty( content ) ) { + return sprintf( + /* translators: accessibility text. %s: heading level. */ + __( 'Level %s. Empty.' ), + level + ); } + + return sprintf( + /* translators: accessibility text. 1: heading level. 2: heading content. */ + __( 'Level %1$s. %2$s' ), + level, + content + ); }, transforms, deprecated, diff --git a/packages/block-library/src/image/index.js b/packages/block-library/src/image/index.js index 332ab1afb062ca..0988b863757af4 100644 --- a/packages/block-library/src/image/index.js +++ b/packages/block-library/src/image/index.js @@ -41,22 +41,18 @@ export const settings = { }, { name: 'rounded', label: _x( 'Rounded', 'block style' ) }, ], - __experimentalLabel( attributes, { context } ) { - if ( context === 'accessibility' ) { - const { caption, alt, url } = attributes; - - if ( ! url ) { - return __( 'Empty' ); - } - - if ( ! alt ) { - return caption || ''; - } + __experimentalGetAccessibilityLabel( { caption, alt, url } ) { + if ( ! url ) { + return __( 'Empty' ); + } - // This is intended to be read by a screen reader. - // A period simply means a pause, no need to translate it. - return alt + ( caption ? '. ' + caption : '' ); + if ( ! alt ) { + return caption || ''; } + + // This is intended to be read by a screen reader. + // A period simply means a pause, no need to translate it. + return alt + ( caption ? '. ' + caption : '' ); }, transforms, getEditWrapperProps( attributes ) { diff --git a/packages/block-library/src/missing/index.js b/packages/block-library/src/missing/index.js index d399dc470946c2..4d82fd099182c3 100644 --- a/packages/block-library/src/missing/index.js +++ b/packages/block-library/src/missing/index.js @@ -26,20 +26,16 @@ export const settings = { html: false, reusable: false, }, - __experimentalLabel( attributes, { context } ) { - if ( context === 'accessibility' ) { - const { originalName } = attributes; + __experimentalGetAccessibilityLabel( { originalName } ) { + const originalBlockType = originalName + ? getBlockType( originalName ) + : undefined; - const originalBlockType = originalName - ? getBlockType( originalName ) - : undefined; - - if ( originalBlockType ) { - return originalBlockType.settings.title || originalName; - } - - return ''; + if ( originalBlockType ) { + return originalBlockType.settings.title || originalName; } + + return ''; }, edit, save, diff --git a/packages/block-library/src/more/index.js b/packages/block-library/src/more/index.js index c70c5b5412e556..a3baa46446b7ef 100644 --- a/packages/block-library/src/more/index.js +++ b/packages/block-library/src/more/index.js @@ -29,10 +29,8 @@ export const settings = { multiple: false, }, example: {}, - __experimentalLabel( attributes, { context } ) { - if ( context === 'accessibility' ) { - return attributes.customText; - } + __experimentalGetAccessibilityLabel( { customText } ) { + return customText; }, transforms, edit, diff --git a/packages/block-library/src/navigation-link/index.js b/packages/block-library/src/navigation-link/index.js index b53e4003c0ec01..7bcc5f6d4123dc 100644 --- a/packages/block-library/src/navigation-link/index.js +++ b/packages/block-library/src/navigation-link/index.js @@ -28,7 +28,11 @@ export const settings = { html: false, }, - __experimentalLabel: ( { label } ) => label, + __experimentalLabel: 'label', + + __experimentalGetAccessibilityLabel( { label } ) { + return label; + }, edit, save, diff --git a/packages/block-library/src/paragraph/index.js b/packages/block-library/src/paragraph/index.js index 1e70972e29d112..d5bddcbffdfe64 100644 --- a/packages/block-library/src/paragraph/index.js +++ b/packages/block-library/src/paragraph/index.js @@ -40,11 +40,8 @@ export const settings = { className: false, __unstablePasteTextInline: true, }, - __experimentalLabel( attributes, { context } ) { - if ( context === 'accessibility' ) { - const { content } = attributes; - return isEmpty( content ) ? __( 'Empty' ) : content; - } + __experimentalGetAccessibilityLabel( { content } ) { + return isEmpty( content ) ? __( 'Empty' ) : content; }, transforms, deprecated, diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index 131175c89568e4..885d98bac5d348 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -57,8 +57,6 @@ export { isUnmodifiedDefaultBlock, normalizeIconObject, isValidIcon, - getBlockLabel as __experimentalGetBlockLabel, - getAccessibleBlockLabel as __experimentalGetAccessibleBlockLabel, } from './utils'; export { doBlocksMatchTemplate, diff --git a/packages/blocks/src/api/test/utils.js b/packages/blocks/src/api/test/utils.js index c00f22cf28b6ea..3fe75a6cf4e9c9 100644 --- a/packages/blocks/src/api/test/utils.js +++ b/packages/blocks/src/api/test/utils.js @@ -13,11 +13,7 @@ import { registerBlockType, setDefaultBlockName, } from '../registration'; -import { - isUnmodifiedDefaultBlock, - getAccessibleBlockLabel, - getBlockLabel, -} from '../utils'; +import { isUnmodifiedDefaultBlock } from '../utils'; describe( 'block helpers', () => { beforeAll( () => { @@ -107,108 +103,3 @@ describe( 'block helpers', () => { } ); } ); } ); - -describe( 'getBlockLabel', () => { - it( 'returns only the block title when the block has no `getLabel` function', () => { - const blockType = { title: 'Recipe' }; - const attributes = {}; - - expect( getBlockLabel( blockType, attributes ) ).toBe( 'Recipe' ); - } ); - - it( 'returns only the block title when the block has a `getLabel` function, but it returns a falsey value', () => { - const blockType = { title: 'Recipe', __experimentalLabel: () => '' }; - const attributes = {}; - - expect( getBlockLabel( blockType, attributes ) ).toBe( 'Recipe' ); - } ); - - it( 'returns the block title with the label when the `getLabel` function returns a value', () => { - const blockType = { - title: 'Recipe', - __experimentalLabel: ( { heading } ) => heading, - }; - const attributes = { heading: 'Cupcakes!' }; - - expect( getBlockLabel( blockType, attributes ) ).toBe( 'Cupcakes!' ); - } ); - - it( 'removes any html elements from the output of the `getLabel` function', () => { - const blockType = { - title: 'Recipe', - __experimentalLabel: ( { heading } ) => heading, - }; - const attributes = { - heading: 'Cupcakes!', - }; - - expect( getBlockLabel( blockType, attributes ) ).toBe( 'Cupcakes!' ); - } ); -} ); - -describe( 'getAccessibleBlockLabel', () => { - it( 'returns only the block title when the block has no `getLabel` function', () => { - const blockType = { title: 'Recipe' }; - const attributes = {}; - - expect( getAccessibleBlockLabel( blockType, attributes ) ).toBe( - 'Recipe Block' - ); - } ); - - it( 'returns only the block title when the block has a `getLabel` function, but it returns a falsey value', () => { - const blockType = { title: 'Recipe', __experimentalLabel: () => '' }; - const attributes = {}; - - expect( getAccessibleBlockLabel( blockType, attributes ) ).toBe( - 'Recipe Block' - ); - } ); - - it( 'returns the block title with the label when the `getLabel` function returns a value', () => { - const blockType = { - title: 'Recipe', - __experimentalLabel: ( { heading } ) => heading, - }; - const attributes = { heading: 'Cupcakes!' }; - - expect( getAccessibleBlockLabel( blockType, attributes ) ).toBe( - 'Recipe Block. Cupcakes!' - ); - } ); - - it( 'removes any html elements from the output of the `getLabel` function', () => { - const blockType = { - title: 'Recipe', - __experimentalLabel: ( { heading } ) => heading, - }; - const attributes = { - heading: 'Cupcakes!', - }; - - expect( getAccessibleBlockLabel( blockType, attributes ) ).toBe( - 'Recipe Block. Cupcakes!' - ); - } ); - - it( 'outputs the block title and label with a row number indicating the position of the block, when the optional third parameter is provided', () => { - const blockType = { - title: 'Recipe', - __experimentalLabel: ( { heading } ) => heading, - }; - const attributes = { heading: 'Cupcakes!' }; - - expect( getAccessibleBlockLabel( blockType, attributes, 3 ) ).toBe( - 'Recipe Block. Row 3. Cupcakes!' - ); - } ); - - it( 'outputs just the block title and row number when there no label is available for the block', () => { - const blockType = { title: 'Recipe' }; - const attributes = {}; - - expect( getAccessibleBlockLabel( blockType, attributes, 3 ) ).toBe( - 'Recipe Block. Row 3' - ); - } ); -} ); diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index 11b333d8bdef23..7ec2499645e586 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -8,8 +8,6 @@ import { default as tinycolor, mostReadable } from 'tinycolor2'; * WordPress dependencies */ import { Component, isValidElement } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; -import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; /** * Internal dependencies @@ -128,107 +126,3 @@ export function normalizeBlockType( blockTypeOrName ) { return blockTypeOrName; } - -/** - * Get the label for the block, usually this is either the block title, - * or the value of the block's `label` function when that's specified. - * - * @param {Object} blockType The block type. - * @param {Object} attributes The values of the block's attributes. - * @param {Object} context The intended use for the label. - * - * @return {string} The block label. - */ -export function getBlockLabel( blockType, attributes, context = 'visual' ) { - const { __experimentalLabel: getLabel, title } = blockType; - - const label = getLabel && getLabel( attributes, { context } ); - - if ( ! label ) { - return title; - } - - // Strip any HTML (i.e. RichText formatting) before returning. - return stripHTML( label ); -} - -/** - * Get a label for the block for use by screenreaders, this is more descriptive - * than the visual label and includes the block title and the value of the - * `getLabel` function if it's specified. - * - * @param {Object} blockType The block type. - * @param {Object} attributes The values of the block's attributes. - * @param {?number} position The position of the block in the block list. - * @param {string} [direction='vertical'] The direction of the block layout. - * - * @return {string} The block label. - */ -export function getAccessibleBlockLabel( - blockType, - attributes, - position, - direction = 'vertical' -) { - // `title` is already localized, `label` is a user-supplied value. - const { title } = blockType; - const label = getBlockLabel( blockType, attributes, 'accessibility' ); - const hasPosition = position !== undefined; - - // getBlockLabel returns the block title as a fallback when there's no label, - // if it did return the title, this function needs to avoid adding the - // title twice within the accessible label. Use this `hasLabel` boolean to - // handle that. - const hasLabel = label && label !== title; - - if ( hasPosition && direction === 'vertical' ) { - if ( hasLabel ) { - return sprintf( - /* translators: accessibility text. %1: The block title, %2: The block row number, %3: The block label.. */ - __( '%1$s Block. Row %2$d. %3$s' ), - title, - position, - label - ); - } - - return sprintf( - /* translators: accessibility text. %s: The block title, %d The block row number. */ - __( '%s Block. Row %d' ), - title, - position - ); - } else if ( hasPosition && direction === 'horizontal' ) { - if ( hasLabel ) { - return sprintf( - /* translators: accessibility text. %1: The block title, %2: The block column number, %3: The block label.. */ - __( '%1$s Block. Column %2$d. %3$s' ), - title, - position, - label - ); - } - - return sprintf( - /* translators: accessibility text. %s: The block title, %d The block column number. */ - __( '%s Block. Column %d' ), - title, - position - ); - } - - if ( hasLabel ) { - return sprintf( - /* translators: accessibility text. %1: The block title. %2: The block label. */ - __( '%1$s Block. %2$s' ), - title, - label - ); - } - - return sprintf( - /* translators: accessibility text. %s: The block title. */ - __( '%s Block' ), - title - ); -} diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js index c1ae1fbc17395d..e4fcf79eb64720 100644 --- a/packages/dom/src/dom.js +++ b/packages/dom/src/dom.js @@ -7,7 +7,7 @@ import { includes } from 'lodash'; * Browser dependencies */ -const { DOMParser, getComputedStyle } = window; +const { getComputedStyle } = window; const { TEXT_NODE, ELEMENT_NODE, @@ -678,15 +678,3 @@ export function wrap( newNode, referenceNode ) { referenceNode.parentNode.insertBefore( newNode, referenceNode ); newNode.appendChild( referenceNode ); } - -/** - * Removes any HTML tags from the provided string. - * - * @param {string} html The string containing html. - * - * @return {string} The text content with any html removed. - */ -export function __unstableStripHTML( html ) { - const document = new DOMParser().parseFromString( html, 'text/html' ); - return document.body.textContent || ''; -} diff --git a/packages/dom/src/test/dom.js b/packages/dom/src/test/dom.js index a2352d75efbd8a..71725bea36c0b4 100644 --- a/packages/dom/src/test/dom.js +++ b/packages/dom/src/test/dom.js @@ -5,7 +5,6 @@ import { isHorizontalEdge, placeCaretAtHorizontalEdge, isTextField, - __unstableStripHTML as stripHTML, } from '../dom'; describe( 'DOM', () => { @@ -157,18 +156,4 @@ describe( 'DOM', () => { ); } ); } ); - - describe( 'stripHTML', () => { - it( 'removes any HTML from a text string', () => { - expect( stripHTML( 'This is emphasized' ) ).toBe( - 'This is emphasized' - ); - } ); - - it( 'removes script tags, but does not execute them', () => { - const html = 'This will not '; - expect( stripHTML( html ) ).toBe( 'This will not throw "Error"' ); - expect( () => stripHTML( html ) ).not.toThrow(); - } ); - } ); } ); diff --git a/packages/e2e-tests/specs/editor/blocks/columns.test.js b/packages/e2e-tests/specs/editor/blocks/columns.test.js index 6255f6c5c5ac8d..2427c1d6f80e07 100644 --- a/packages/e2e-tests/specs/editor/blocks/columns.test.js +++ b/packages/e2e-tests/specs/editor/blocks/columns.test.js @@ -20,7 +20,7 @@ describe( 'Columns', () => { await page.click( '[aria-label="Block navigation"]' ); const columnBlockMenuItem = ( await page.$x( - '//button[contains(concat(" ", @class, " "), " block-editor-block-navigation__item-button ")][text()="Column"]' + '//button[contains(concat(" ", @class, " "), " block-editor-block-navigation-item__button ")][text()="Column"]' ) )[ 0 ]; await columnBlockMenuItem.click(); diff --git a/packages/e2e-tests/specs/editor/plugins/block-icons.test.js b/packages/e2e-tests/specs/editor/plugins/block-icons.test.js index 517340e987b80a..8755d171f678b1 100644 --- a/packages/e2e-tests/specs/editor/plugins/block-icons.test.js +++ b/packages/e2e-tests/specs/editor/plugins/block-icons.test.js @@ -39,7 +39,7 @@ async function getFirstInserterIcon() { async function selectFirstBlock() { await pressKeyWithModifier( 'access', 'o' ); const navButtons = await page.$$( - '.block-editor-block-navigation__item-button' + '.block-editor-block-navigation-item__button' ); await navButtons[ 0 ].click(); } diff --git a/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js index 643998905e00e3..b45a7aa8f721f1 100644 --- a/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js @@ -12,7 +12,7 @@ import { async function openBlockNavigator() { await pressKeyWithModifier( 'access', 'o' ); await page.waitForSelector( - '.block-editor-block-navigation__item-button.is-selected' + '.block-editor-block-navigation-item__button.is-selected' ); } @@ -37,7 +37,7 @@ describe( 'Navigating the block hierarchy', () => { await page.click( '[aria-label="Block navigation"]' ); const columnsBlockMenuItem = ( await page.$x( - "//button[contains(@class,'block-editor-block-navigation__item') and contains(text(), 'Columns')]" + "//button[contains(@class,'block-editor-block-navigation-item__button') and contains(text(), 'Columns')]" ) )[ 0 ]; await columnsBlockMenuItem.click(); @@ -55,7 +55,7 @@ describe( 'Navigating the block hierarchy', () => { await page.click( '[aria-label="Block navigation"]' ); const lastColumnsBlockMenuItem = ( await page.$x( - "//button[contains(@class,'block-editor-block-navigation__item') and contains(text(), 'Column')]" + "//button[contains(@class,'block-editor-block-navigation-item__button') and contains(text(), 'Column')]" ) )[ 3 ]; await lastColumnsBlockMenuItem.click(); diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md index 30ed4296d162f7..67cf1548e8deac 100644 --- a/packages/rich-text/README.md +++ b/packages/rich-text/README.md @@ -335,6 +335,18 @@ _Returns_ - `string`: HTML string. +# **toPlainText** + +Removes any HTML tags from the provided string. + +_Parameters_ + +- _html_ `string`: The string containing html. + +_Returns_ + +- `string`: The text content with any html removed. + # **unregisterFormatType** Unregisters a format. diff --git a/packages/rich-text/src/index.js b/packages/rich-text/src/index.js index ac4cea67d4757c..014d010f464e6a 100644 --- a/packages/rich-text/src/index.js +++ b/packages/rich-text/src/index.js @@ -26,6 +26,7 @@ export { slice } from './slice'; export { split } from './split'; export { toDom as __unstableToDom } from './to-dom'; export { toHTMLString } from './to-html-string'; +export { toPlainText } from './to-plain-text'; export { toggleFormat } from './toggle-format'; export { LINE_SEPARATOR as __UNSTABLE_LINE_SEPARATOR } from './special-characters'; export { unregisterFormatType } from './unregister-format-type'; diff --git a/packages/rich-text/src/test/to-plain-text.js b/packages/rich-text/src/test/to-plain-text.js new file mode 100644 index 00000000000000..bc5883903f906f --- /dev/null +++ b/packages/rich-text/src/test/to-plain-text.js @@ -0,0 +1,27 @@ +/** + * Internal dependencies + */ +import { toPlainText } from '../to-plain-text'; + +describe( 'toPlainText', () => { + beforeAll( () => { + // Initialize the rich-text store. + require( '../store' ); + } ); + + it( 'removes any HTML from a text string', () => { + expect( toPlainText( 'This is emphasized' ) ).toBe( 'This is emphasized' ); + } ); + + it( 'removes script tags, but does not execute them', () => { + const html = 'This will not '; + expect( toPlainText( html ) ).toBe( 'This will not throw "Error"' ); + expect( () => toPlainText( html ) ).not.toThrow(); + } ); + + it( 'expects strings and an empty string for falsey values', () => { + expect( toPlainText( '' ) ).toBe( '' ); + expect( toPlainText( undefined ) ).toBe( '' ); + expect( toPlainText( null ) ).toBe( '' ); + } ); +} ); diff --git a/packages/rich-text/src/to-plain-text.js b/packages/rich-text/src/to-plain-text.js new file mode 100644 index 00000000000000..6bc12442a56967 --- /dev/null +++ b/packages/rich-text/src/to-plain-text.js @@ -0,0 +1,19 @@ +/** + * Internal dependencies + */ +import { create } from './create'; + +/** + * Removes any HTML tags from the provided string. + * + * @param {string} html The string containing html. + * + * @return {string} The text content with any html removed. + */ +export function toPlainText( html ) { + if ( ! html ) { + return ''; + } + + return create( { html } ).text; +}