-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Try better block labels #18132
Conversation
packages/blocks/package.json
Outdated
@@ -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", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is definitely something that should happen. I consider it a blocker personally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ellatrix - can you help to extract the related logic from @wordpress/rich-text
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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, ' - ' ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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` } );
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a very good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, thanks for spotting that @pinarol!
packages/blocks/src/api/utils.js
Outdated
const richTextValue = create( { html: attributes[ displayNameAttribute ] } ); | ||
const formatlessDisplayName = getTextContent( richTextValue ); | ||
|
||
return `${ blockTitle }${ separator }${ formatlessDisplayName }`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if I wanted to avoid using the block title and separator format? Currently, we are working on some blocks that look like this:
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
For Navigation block I think we should keep it as it is, which is much easier to browse in the navigator. |
a5b655a
to
619331a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a spelling error suggestion.
Thanks for spotting that @MarcoZehe! |
bfdf709
to
07e2ee0
Compare
07e2ee0
to
516c05c
Compare
ed80aa9
to
40ae5cd
Compare
const [ blockLabel, setBlockLabel ] = useState( '' ); | ||
|
||
useEffect( () => { | ||
const timeoutId = setTimeout( () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why a timeout?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably because of https://github.com/WordPress/gutenberg/pull/18132/files#r359241034
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is being removed in #19597
|
||
return blockLabel; | ||
}; | ||
|
||
function BlockListBlock( { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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' ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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' ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
__experimentalLabel: getLabel, | |
__experimentalGetLabel: getLabel, |
__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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
* @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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm now working on having two separate params in the block definition, so this won't be an issue any longer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
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. |
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? |
@MarcoZehe Yes, no worries, it's good to improve gradually. :) |
@aduth @youknowriad @ellatrix @MarcoZehe I've addressed most of the remaining feedback in #19664. Thanks for the reviews and patience! |
Hi @talldan, is this API ready to be stabilized in WordPress 5.4? |
Hi @jorgefilipecosta, at the moment there's still some follow-up work to do on on these APIs, so I think they'll have to remain experimental for 5.4. |
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: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:
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.
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:
Other blocks worth testing for the aria-label - Paragraph, Heading, More, Image.
Types of changes
New feature (non-breaking change which adds functionality)
Checklist: