Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try better block labels #18132

Merged
merged 4 commits into from Jan 10, 2020
Merged

Try better block labels #18132

merged 4 commits into from Jan 10, 2020

Conversation

@talldan
Copy link
Contributor

talldan commented Oct 28, 2019

Description

Related #17519, #18014

Introduces a new __experimentalLabel( attributes, context ) function to the block api. The aim of this is to provide more context about a particular block which can be currently surfaced in two ways:

  1. Instead of displaying the Block Title in the Block Navigator, it displays some content from the block. This is currently being used by the Navigation Block to let users differentiate their nav links:
    Screen Shot 2019-12-11 at 3 38 30 pm

  2. Providing more contextual information to screen readers, this might be paragraph text or an image caption so that users can be more sure of the block content.
    Screen Shot 2019-12-11 at 4 09 18 pm

The new __experimentalLabel also replaces the mobile function __experimentalGetAccessibilityLabel and those labels now work across both mobile and web.

The blocks package now also has two new convenience functions for retrieving these labels:

  • __experimentalGetBlockLabel - returns just the output of the __experimentalLabel.
  • __experimentalGetAccessibleBlockLabel - returns some extra information useful to screen readers like the block title and the position in the block list.

Those functions will also strip any html (i.e. from formatting) from the labels.

How has this been tested?

Additional unit testing.

Manual testing:

  1. Add a Navigation Block (make sure it's enabled as an experimental feature).
  2. Ensure the navigation block has menu items.
  3. View the Block navigator.
  4. Observe the block labels for menu items
  5. Inspect the block mover for a menu item using dev tools
  6. Observe the screen reader text includes the new block label
  7. Inspect the menu item's block wrapper
  8. Observe the aria label includes the new block label

Other blocks worth testing for the aria-label - Paragraph, Heading, More, Image.

Types of changes

New feature (non-breaking change which adds functionality)

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
@@ -33,6 +33,7 @@
"@wordpress/html-entities": "file:../html-entities",
"@wordpress/i18n": "file:../i18n",
"@wordpress/is-shallow-equal": "file:../is-shallow-equal",
"@wordpress/rich-text": "file:../rich-text",

This comment has been minimized.

Copy link
@talldan

talldan Oct 28, 2019

Author Contributor

I imagine adding RichText as a dependency here is something to avoid. At the moment this is used to strip formatting/html from the attribute, but an alternative could be to add something to @wordpress/dom that does this.

This comment has been minimized.

Copy link
@gziolo

gziolo Oct 28, 2019

Member

Yes, this is definitely something that should happen. I consider it a blocker personally.

This comment has been minimized.

Copy link
@gziolo

gziolo Oct 28, 2019

Member

@ellatrix - can you help to extract the related logic from @wordpress/rich-text?

This comment has been minimized.

Copy link
@talldan

talldan Dec 11, 2019

Author Contributor

I've now added stripHTML to the dom package in 7bf4683, and I'm using that.

For the implementation of that function I used some stack overflow driven development:
https://stackoverflow.com/questions/822452/strip-html-from-text-javascript/47140708#47140708

As far as I can tell this works across platform. MDN has a ? against mobile safari in its browser support chart (https://developer.mozilla.org/en-US/docs/Web/API/DOMParser), but it worked in my simulator.

@mtias

This comment has been minimized.

Copy link
Contributor

mtias commented Oct 28, 2019

I don't think we need the "Menu Item" prefix there when we are already under a "Navigation Menu" block.

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 blockLabel = sprintf( __( 'Block: %s' ), __experimentalGetBlockLabel( blockType, attributes, ' - ' ) );

This comment has been minimized.

Copy link
@gziolo

gziolo Oct 28, 2019

Member

@hypest @koke @Tug - it didn't take a lot of time to see this experimental method useful in the web context as well :)

This comment has been minimized.

Copy link
@pinarol

pinarol Nov 8, 2019

Contributor

Hmm it is a different method though. The one used on mobile is __experimentalGetAccessibilityLabel, input parameters and the content returned is different as well. __experimentalGetAccessibilityLabel is for constructing a custom description for the block to be read by VoiceOver. It can be a long description too. For example it reads the caption and the alt text for the image block. AFAIU __experimentalGetBlockLabel is used in places where a short description of block is needed.

This comment has been minimized.

Copy link
@gziolo

gziolo Nov 8, 2019

Member

It would be great to have one method which would cover all those use cases through some sort of context. In your case it could be:

getBlockLabel( clientId, { context: 'screen-reader` } );

This comment has been minimized.

Copy link
@pinarol

pinarol Nov 8, 2019

Contributor

Sounds like a very good idea.

This comment has been minimized.

Copy link
@talldan

talldan Dec 3, 2019

Author Contributor

Yep, they're a bit different at the moment. I'll have a look into it and see what the options are for consolidating the two.

This comment has been minimized.

Copy link
@MarcoZehe

MarcoZehe Dec 4, 2019

Contributor

The Image block and Paragraph block descriptions could be pretty long for accessibility labels, but I think the others could work pretty well for both accessible and visual block labels.

This is usually not a problem if there is no important information after that long text. So if block type, level, coordinates etc., all come first, and then the actual text is last, users can always interrupt speech once they know what that block is about. One could artificially limit that character count to 100 or 150, but that runs the potential of not being enough for some.

The friends at Gutenberg Mobile already do this block labeling in their navigation layer, at least for VoiceOver on iOS. Perhaps look at what they're doing and mirror that?

This comment has been minimized.

Copy link
@talldan

talldan Dec 4, 2019

Author Contributor

I've consolidated the functionality now so that both mobile and web code uses __experimentalGetAccessibilityLabel and it should return the same format as the mobile function produced previously. I haven't tested it beyond unit tests for the function, though.

@pinarol Let me know what you think.

I think there's merit in what @gziolo proposes for the context of the labels. At the moment there's only the navigation link block that has a visual label, but others like the image, paragraph and heading block might require a slightly different label (or in some cases, no label at all).

This comment has been minimized.

Copy link
@talldan

talldan Dec 4, 2019

Author Contributor

@MarcoZehe Thanks for the feedback! For some reason I didn't see your message until after the one I posted above.

I've kept the format of the text broadly the same across web and mobile, it follows the pattern established on mobile. Having said that the labels for each block themselves still need to be implemented separately on web as they're in different files. I plan to look at some of this next.

This comment has been minimized.

Copy link
@pinarol

pinarol Dec 5, 2019

Contributor

@talldan this is looking good overall. But there's just one thing that sounds odd when VoiceOver is on and it should be fixed. If there's an unrecognized block the screen reader says 'Unrecognized block block', so there's an extra block at the end. I think it is related with the removal of this logic:

if ( name === 'core/missing' ) { // is the block unrecognized?	
			blockName = title;	
}

Missing blocks are already coming with 'Unrecognized Block' title.

The rest looks good to me.

This comment has been minimized.

Copy link
@talldan

talldan Dec 5, 2019

Author Contributor

Cool, thanks for spotting that @pinarol!

const richTextValue = create( { html: attributes[ displayNameAttribute ] } );
const formatlessDisplayName = getTextContent( richTextValue );

return `${ blockTitle }${ separator }${ formatlessDisplayName }`;

This comment has been minimized.

Copy link
@noahtallen

noahtallen Oct 29, 2019

Contributor

What if I wanted to avoid using the block title and separator format? Currently, we are working on some blocks that look like this:

Before:
Screen Shot 2019-10-28 at 6 34 54 PM

After:
Screen Shot 2019-10-28 at 6 34 10 PM

I think we'd like to just use "Header" instead of displaying "Template: Header". To us, the "Template" block (Which is dubiously named. One shouldn't trust what they think "template" means! 😆) is just a transparent wrapper around some other blocks and isn't really meant to be understood by or visible to the user.

This comment has been minimized.

Copy link
@talldan

talldan Oct 29, 2019

Author Contributor

Good to know. A previous proposal has been to use a function or component to create the label, which is useful if the label is created from multiple attributes.

__experimentalDisplayName( { attributes, blockType } ) {
  return `${ blockType.title }: ${ attributes.label }`
}

My concern would be a lack of consistency across blocks. Also where this is used as an aria-label, the block type might make sense, whereas in the navigator it might not.

I suppose this could be customisable by getBlockLabel:

getBlockLabel( blockType, blockAttributes, { includeBlockTitle: false } );

So that aria-labels can include the block titles, but the navigator doesn't.

This comment has been minimized.

Copy link
@gziolo

gziolo Oct 29, 2019

Member

How about the 3rd param is more general and contains properties like:

{
    context: 'block-navigation',
    format: 'concise',
}

Then each block's author could decide how to code the label depending on the properties passed. We could always provide a default handler which works fine in 90% of cases.

This comment has been minimized.

Copy link
@mtias

mtias Nov 8, 2019

Contributor

I'm still doubtful of the use case in general. We are not going to be using "Menu Item: Label" for navigation, for example. If we'd also not use it for template parts, we might be preemptively overcomplicating things here.

This comment has been minimized.

Copy link
@talldan

talldan Dec 3, 2019

Author Contributor

I'm thinking the best approach might be to have two functions getAccessibleBlockLabel and getVisualBlockLabel. The former can include the block title for some extra context. Thoughts?

I've rebased and made the change described above, though it seems at the moment there's a bug with the nav block in master, so hard to test it.

@talldan talldan mentioned this pull request Nov 8, 2019
5 of 12 tasks complete
@mtias

This comment has been minimized.

Copy link
Contributor

mtias commented Nov 8, 2019

For Navigation block I think we should keep it as it is, which is much easier to browse in the navigator.

Copy link
Contributor

MarcoZehe left a comment

Left a spelling error suggestion.

packages/blocks/src/api/index.js Outdated Show resolved Hide resolved
packages/blocks/src/api/test/utils.js Outdated Show resolved Hide resolved
packages/blocks/src/api/test/utils.js Outdated Show resolved Hide resolved
packages/blocks/src/api/test/utils.js Outdated Show resolved Hide resolved
packages/blocks/src/api/test/utils.js Outdated Show resolved Hide resolved
packages/blocks/src/api/test/utils.js Outdated Show resolved Hide resolved
packages/blocks/src/api/test/utils.js Outdated Show resolved Hide resolved
packages/blocks/src/api/utils.js Outdated Show resolved Hide resolved
@talldan

This comment has been minimized.

Copy link
Contributor Author

talldan commented Dec 4, 2019

Thanks for spotting that @MarcoZehe!

@talldan talldan force-pushed the try/consistent-block-label branch from bfdf709 to 07e2ee0 Dec 4, 2019
@talldan talldan force-pushed the try/consistent-block-label branch from 07e2ee0 to 516c05c Dec 11, 2019
@talldan talldan force-pushed the try/consistent-block-label branch 2 times, most recently from 2b8e48e to b2eadff Dec 12, 2019
@@ -522,7 +519,7 @@ function BlockListBlock( {
onClick={ onTouchStop }
onKeyDown={ onKeyDown }
tabIndex="0"
aria-label={ blockLabel }
aria-label={ blockAriaLabel }

This comment has been minimized.

Copy link
@talldan

talldan Dec 18, 2019

Author Contributor

This might represent a bit of a performance issue for something like the paragraph block where the aria-label contains all the paragraph text. Typing in the block also updates the aria-label!

An option might be to debounce the function.

This comment has been minimized.

Copy link
@MarcoZehe

MarcoZehe Dec 18, 2019

Contributor

This might represent a bit of a performance issue for something like the paragraph block where the aria-label contains all the paragraph text. Typing in the block also updates the aria-label!

How do the mobile friends do it? An option might be to update the paragraph text, for example, once editing mode is exited.

This comment has been minimized.

Copy link
@talldan

talldan Jan 10, 2020

Author Contributor

I have a feeling it's not as much of an issue on the mobile side as there's no document object model. I've updated the code now so that the function is debounced, hopefully that's an ok solution.

@MarcoZehe

This comment has been minimized.

Copy link
Contributor

MarcoZehe commented Jan 9, 2020

Hi @talldan, do you plan on working on this more?

@talldan talldan force-pushed the try/consistent-block-label branch from 3ab7df9 to 2e07ffc Jan 10, 2020
@talldan

This comment has been minimized.

Copy link
Contributor Author

talldan commented Jan 10, 2020

Hey @MarcoZehe, yep, it should be ready for a review now!

Copy link
Contributor

MarcoZehe left a comment

This looks great from an accessibility standpoint, and also from how I so far understand the code base. But this should definitely be reviewed by actual code owners, too. Nice to see some clean-up happening with those native files. :-) Thank you for working on this!

0,
true,
true,
dirDown,
) ).toBe( `Block ${ type } is the only block, and cannot be moved` );
) ).toBe( `Block ${ label } is the only block, and cannot be moved` );

This comment has been minimized.

Copy link
@MarcoZehe

MarcoZehe Jan 10, 2020

Contributor

I am wondering if it makes sense to also add tests for horizontal movers so they are properly labeled for columns moving left and right. Also, in light of the other discussion, it would make sense to update the movers text to speak of rows in these situations I think. But that should be dealt with in a follow-up after this lands.

This comment has been minimized.

Copy link
@talldan

talldan Jan 10, 2020

Author Contributor

Yep, that's a good point. I've added some tests in #19549. I noticed while working on it that it also looks like the direction part of the description isn't internationalised. I've made an issue for that which should be an easy one for new contributors (#19550)

talldan and others added 4 commits Oct 28, 2019
… 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
@talldan talldan force-pushed the try/consistent-block-label branch from ed80aa9 to 40ae5cd Jan 10, 2020
@draganescu draganescu merged commit a8f43fa into master Jan 10, 2020
2 checks passed
2 checks passed
pull-request-automation
Details
Travis CI - Pull Request Build Passed
Details
@draganescu draganescu deleted the try/consistent-block-label branch Jan 10, 2020
const [ blockLabel, setBlockLabel ] = useState( '' );

useEffect( () => {
const timeoutId = setTimeout( () => {

This comment has been minimized.

Copy link
@youknowriad

youknowriad Jan 10, 2020

Contributor

Why a timeout?

This comment has been minimized.

This comment has been minimized.

Copy link
@talldan

talldan Jan 15, 2020

Author Contributor

Looks like this is being removed in #19597

@@ -560,6 +583,7 @@ const applyWithSelect = withSelect(
hasFixedToolbar: hasFixedToolbar && isLargeViewport,
isLast: index === blockOrder.length - 1,
isNavigationMode: isNavigationMode(),
index,

This comment has been minimized.

Copy link
@youknowriad

youknowriad Jan 10, 2020

Contributor

We removed this explicitely some time ago for performance reasons. Adding this prop rerenders the block everytime a precedent block is removed in the list, this can be harmful for long posts. Also, We should favor the inner useSelect instead of adding new props to this component.

This comment has been minimized.

Copy link
@talldan

talldan Jan 15, 2020

Author Contributor

Looks like this is being removed in #19597


return blockLabel;
};

function BlockListBlock( {

This comment has been minimized.

Copy link
@youknowriad

youknowriad Jan 10, 2020

Contributor

This is one of the most central components of our codebase and changes here can have big performance impacts, so, a sanity check (running performance tests) would be good (with and without the PR.

*
* @return {string} The block label.
*/
export function getAccessibleBlockLabel( blockType, attributes, position, direction = 'vertical' ) {

This comment has been minimized.

Copy link
@youknowriad

youknowriad Jan 10, 2020

Contributor

We've moving slowly away from functions that take blockType, and attributes in favor of the whole block object. I also feel like position and direction are not necessary and can be retrieved from state if this is becomes more a selector or a component instead of a global function. cc @aduth

This comment has been minimized.

Copy link
@aduth

aduth Jan 10, 2020

Member

We've moving slowly away from functions that take blockType, and attributes in favor of the whole block object.

Right. It already raises the question: What if I wanted the label to change depending on the inner blocks? How would that be implemented today with this interface?

Also, there's some inconsistency between this utility and the others in the file; where most all of the other utilities will accept either a block type (object) or a block name (string).

This comment has been minimized.

Copy link
@talldan

talldan Jan 15, 2020

Author Contributor

I mentioned it above in another comment, but I'm now looking at moving this out of the blocks package into block-editor as selectors.

the api would be getAccessibleBlockLabel( clientId, direction ) and the selector implementation could be responsible for grabbing any other data.

// Strip any HTML (i.e. RichText formatting) before returning.
return stripHTML( label );
Comment on lines +149 to +150

This comment has been minimized.

Copy link
@aduth

aduth Jan 10, 2020

Member

Can't we make this the responsibility of the block implementation in generating the label? In other words, force the block to return a plain string. There, we already have some availability of the rich-text tooling. I see there was some prior discussion about rich-text being used here, but if we would put it on the block to return, then it doesn't need to be a dependency here.

I'm not sure there exists a utility already for generating a plain-text string from a RichText value, but it would seem to align with some of the existing utilities (toTree, toDOM, toHTMLString). Something like a toPlainString (cc @ellatrix).

I am a bit worried about introducing something like a stripHTML, both for these reasons above in that it ideally should not be necessary, but also because it could become a security weakness† or a source of poor performance.

† Not to say that it's vulnerable today in how it's used, notably because we're not dangerouslySetInnerHTML, but it's one of those things where we should try to avoid this altogether if we can, for fear of future mis-use, browser inconsistencies, etc.

This comment has been minimized.

Copy link
@talldan

talldan Jan 15, 2020

Author Contributor

So I had some feedback that the __experimentalLabel api on the block needed some thought. It looks like the option is to split it into two separate things:

This for the accessibility label:

__experimentalGetAccessibilityLabel( attributes ) { return attributes.title }

and this for the visual label that might appear in the block navigator:

__experimentalLabel: 'title' // title is the name of the attribute

So it looks like the option of moving this responsibility to the block might not be possible for the latter. One option is something that @youknowriad mentioned in another comment, is that these could become selectors in the block-editor package. I started working on that, and I think that might be a good option.

Then I could change stripHTML to toPlainString (and move it to rich-text) as suggested.

*
* @return {string} The block label.
*/
export function getAccessibleBlockLabel( blockType, attributes, position, direction = 'vertical' ) {

This comment has been minimized.

Copy link
@aduth

aduth Jan 10, 2020

Member

We've moving slowly away from functions that take blockType, and attributes in favor of the whole block object.

Right. It already raises the question: What if I wanted the label to change depending on the inner blocks? How would that be implemented today with this interface?

Also, there's some inconsistency between this utility and the others in the file; where most all of the other utilities will accept either a block type (object) or a block name (string).

*/
export function getBlockLabel( blockType, attributes, context = 'visual' ) {
const {
__experimentalLabel: getLabel,

This comment has been minimized.

Copy link
@aduth

aduth Jan 10, 2020

Member

Was it intentional that this was called __experimentalLabel and not __experimentalGetLabel, as the deconstructed assignment would imply that its stable form might take?

I would expect either one of the following:

Suggested change
__experimentalLabel: getLabel,
__experimentalGetLabel: getLabel,
Suggested change
__experimentalLabel: getLabel,
__experimentalLabel: label,

While I'd grant that most functions of a block type don't have these sorts of prefixes ("get"), the reason for that as I'd see it is that they're all (to my knowledge) already in verb forms anyways: "save", "edit", etc.

This comment has been minimized.

Copy link
@talldan

talldan Jan 15, 2020

Author Contributor

👍 Yep, I wasn't sure about this, thanks for clarifying.

*
* @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.

This comment has been minimized.

Copy link
@aduth

aduth Jan 10, 2020

Member

This isn't an object. If we documented this as an union type of string literals, I think it would have helped clarify (both for us as the implementers and the future consumers) which sorts of values we're expecting to be supported.

Suggested change
* @param {Object} context The intended use for the label.
* @param {"visual"|"accessibility"} context The intended use for the label

It is a bit confusing for me though in how we expect block implementers to be handling this. Even in the implementations we've revised so far, we're only ever handling the "accessibility" case. So what's the point of "visual"? And how would we expect to treat the undefined return value from those implementations? Should have gone ahead and returned a value for the "visual" context for existing block revisions? (Getting a bit of YAGNI vibes here)

This comment has been minimized.

Copy link
@talldan

talldan Jan 15, 2020

Author Contributor

I'm now working on having two separate params in the block definition, so this won't be an issue any longer.

This comment has been minimized.

Copy link
@aduth

aduth Jan 17, 2020

Member

I'm now working on having two separate params in the block definition, so this won't be an issue any longer.

What about the second part of my comment, in which I question whether we even need two?

It is a bit confusing for me though in how we expect block implementers to be handling this. Even in the implementations we've revised so far, we're only ever handling the "accessibility" case. So what's the point of "visual"? And how would we expect to treat the undefined return value from those implementations? Should have gone ahead and returned a value for the "visual" context for existing block revisions? (Getting a bit of YAGNI vibes here)

@koke koke mentioned this pull request Jan 13, 2020
2 of 2 tasks complete
@ellatrix

This comment has been minimized.

Copy link
Member

ellatrix commented Jan 13, 2020

I'm not super fond of the new label on the block wrapper elements, since we'll eventually remove this wrapper element entirely. Additionally, this seems more useful in navigation mode, where it's not added at all.

@MarcoZehe

This comment has been minimized.

Copy link
Contributor

MarcoZehe commented Jan 13, 2020

Yes, indeed these make the most sense on the buttons that get focus in navigation mode. Sorry I didn't catch that during my review. I believe moving these in a follow-up PR should be feasible, right?

@ellatrix

This comment has been minimized.

Copy link
Member

ellatrix commented Jan 13, 2020

@MarcoZehe Yes, no worries, it's good to improve gradually. :)

@ellatrix ellatrix mentioned this pull request Jan 13, 2020
0 of 6 tasks complete
@talldan talldan mentioned this pull request Jan 15, 2020
6 of 6 tasks complete
@talldan

This comment has been minimized.

Copy link
Contributor Author

talldan commented Jan 15, 2020

@aduth @youknowriad @ellatrix @MarcoZehe I've addressed most of the remaining feedback in #19664. Thanks for the reviews and patience!

@ellatrix ellatrix added this to the Gutenberg 7.3 milestone Jan 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.