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

Fix useFocusFirstElement in non-contenteditable Blocks #37934

Merged
merged 39 commits into from Mar 15, 2022

Conversation

alexstine
Copy link
Contributor

@alexstine alexstine commented Jan 13, 2022

Description

Fixes useFocusFirstElement to take in to account non-contenteditable Blocks. In this case, it performs a secondary search to find a more suitable focus point rather than inserting the caret at the edge which doesn't work well for Blocks without contenteditable.

How has this been tested?

Tested in Firefox with NVDA screen reader on Windows 10.

  1. Open the Block Editor.
  2. Insert a Columns Block.
  3. Focus is moved to the first focussable element which is the Block Variations panel.

Screenshots

Types of changes

Enhancement

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • I've tested my changes with keyboard and screen readers.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR (please manually search all *.native.js files for terms that need renaming or removal).
  • I've updated related schemas if appropriate.

@github-actions
Copy link

@github-actions github-actions bot commented Jan 13, 2022

Size Change: +71 B (0%)

Total Size: 1.16 MB

Filename Size Change
build/block-editor/index.min.js 144 kB +73 B (0%)
build/block-library/index.min.js 168 kB -2 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 993 B
build/admin-manifest/index.min.js 1.24 kB
build/annotations/index.min.js 2.77 kB
build/api-fetch/index.min.js 2.27 kB
build/autop/index.min.js 2.15 kB
build/blob/index.min.js 487 B
build/block-directory/index.min.js 6.49 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-editor/style-rtl.css 14.8 kB
build/block-editor/style.css 14.8 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 445 B
build/block-library/blocks/button/editor.css 445 B
build/block-library/blocks/button/style-rtl.css 560 B
build/block-library/blocks/button/style.css 560 B
build/block-library/blocks/buttons/editor-rtl.css 292 B
build/block-library/blocks/buttons/editor.css 292 B
build/block-library/blocks/buttons/style-rtl.css 275 B
build/block-library/blocks/buttons/style.css 275 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 103 B
build/block-library/blocks/code/style.css 103 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-template/style-rtl.css 127 B
build/block-library/blocks/comment-template/style.css 127 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-query-loop/editor-rtl.css 95 B
build/block-library/blocks/comments-query-loop/editor.css 95 B
build/block-library/blocks/cover/editor-rtl.css 546 B
build/block-library/blocks/cover/editor.css 547 B
build/block-library/blocks/cover/style-rtl.css 1.56 kB
build/block-library/blocks/cover/style.css 1.56 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 417 B
build/block-library/blocks/embed/style.css 417 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 353 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 965 B
build/block-library/blocks/gallery/editor.css 967 B
build/block-library/blocks/gallery/style-rtl.css 1.61 kB
build/block-library/blocks/gallery/style.css 1.61 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 159 B
build/block-library/blocks/group/editor.css 159 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 114 B
build/block-library/blocks/heading/style.css 114 B
build/block-library/blocks/html/editor-rtl.css 332 B
build/block-library/blocks/html/editor.css 333 B
build/block-library/blocks/image/editor-rtl.css 731 B
build/block-library/blocks/image/editor.css 730 B
build/block-library/blocks/image/style-rtl.css 529 B
build/block-library/blocks/image/style.css 535 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 199 B
build/block-library/blocks/latest-posts/editor.css 198 B
build/block-library/blocks/latest-posts/style-rtl.css 447 B
build/block-library/blocks/latest-posts/style.css 446 B
build/block-library/blocks/list/style-rtl.css 94 B
build/block-library/blocks/list/style.css 94 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 493 B
build/block-library/blocks/media-text/style.css 490 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 708 B
build/block-library/blocks/navigation-link/editor.css 706 B
build/block-library/blocks/navigation-link/style-rtl.css 94 B
build/block-library/blocks/navigation-link/style.css 94 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 299 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation-submenu/view.min.js 375 B
build/block-library/blocks/navigation/editor-rtl.css 2.03 kB
build/block-library/blocks/navigation/editor.css 2.04 kB
build/block-library/blocks/navigation/style-rtl.css 1.89 kB
build/block-library/blocks/navigation/style.css 1.88 kB
build/block-library/blocks/navigation/view.min.js 2.85 kB
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 363 B
build/block-library/blocks/page-list/editor.css 363 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 273 B
build/block-library/blocks/paragraph/style.css 273 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/style-rtl.css 446 B
build/block-library/blocks/post-comments-form/style.css 446 B
build/block-library/blocks/post-comments/style-rtl.css 521 B
build/block-library/blocks/post-comments/style.css 521 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 721 B
build/block-library/blocks/post-featured-image/editor.css 721 B
build/block-library/blocks/post-featured-image/style-rtl.css 153 B
build/block-library/blocks/post-featured-image/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 323 B
build/block-library/blocks/post-template/style.css 323 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 80 B
build/block-library/blocks/post-title/style.css 80 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 389 B
build/block-library/blocks/pullquote/style.css 388 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 234 B
build/block-library/blocks/query-pagination/style.css 231 B
build/block-library/blocks/query/editor-rtl.css 131 B
build/block-library/blocks/query/editor.css 132 B
build/block-library/blocks/quote/style-rtl.css 201 B
build/block-library/blocks/quote/style.css 201 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 397 B
build/block-library/blocks/search/style.css 398 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 99 B
build/block-library/blocks/separator/editor.css 99 B
build/block-library/blocks/separator/style-rtl.css 233 B
build/block-library/blocks/separator/style.css 233 B
build/block-library/blocks/separator/theme-rtl.css 172 B
build/block-library/blocks/separator/theme.css 172 B
build/block-library/blocks/shortcode/editor-rtl.css 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/editor-rtl.css 744 B
build/block-library/blocks/site-logo/editor.css 744 B
build/block-library/blocks/site-logo/style-rtl.css 181 B
build/block-library/blocks/site-logo/style.css 181 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 177 B
build/block-library/blocks/social-link/editor.css 177 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.37 kB
build/block-library/blocks/social-links/style.css 1.36 kB
build/block-library/blocks/spacer/editor-rtl.css 332 B
build/block-library/blocks/spacer/editor.css 332 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 471 B
build/block-library/blocks/table/editor.css 472 B
build/block-library/blocks/table/style-rtl.css 481 B
build/block-library/blocks/table/style.css 481 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 226 B
build/block-library/blocks/tag-cloud/style.css 227 B
build/block-library/blocks/template-part/editor-rtl.css 235 B
build/block-library/blocks/template-part/editor.css 235 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 571 B
build/block-library/blocks/video/editor.css 572 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/common-rtl.css 934 B
build/block-library/common.css 932 B
build/block-library/editor-rtl.css 9.96 kB
build/block-library/editor.css 9.96 kB
build/block-library/reset-rtl.css 474 B
build/block-library/reset.css 474 B
build/block-library/style-rtl.css 11.4 kB
build/block-library/style.css 11.4 kB
build/block-library/theme-rtl.css 665 B
build/block-library/theme.css 670 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 46.4 kB
build/components/index.min.js 217 kB
build/components/style-rtl.css 15.6 kB
build/components/style.css 15.6 kB
build/compose/index.min.js 11.2 kB
build/core-data/index.min.js 14.1 kB
build/customize-widgets/index.min.js 11.2 kB
build/customize-widgets/style-rtl.css 1.39 kB
build/customize-widgets/style.css 1.39 kB
build/data-controls/index.min.js 663 B
build/data/index.min.js 8.05 kB
build/date/index.min.js 31.9 kB
build/deprecated/index.min.js 518 B
build/dom-ready/index.min.js 336 B
build/dom/index.min.js 4.53 kB
build/edit-navigation/index.min.js 16.1 kB
build/edit-navigation/style-rtl.css 4.04 kB
build/edit-navigation/style.css 4.05 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/index.min.js 29.8 kB
build/edit-post/style-rtl.css 7.07 kB
build/edit-post/style.css 7.07 kB
build/edit-site/index.min.js 43.9 kB
build/edit-site/style-rtl.css 7.44 kB
build/edit-site/style.css 7.42 kB
build/edit-widgets/index.min.js 16.5 kB
build/edit-widgets/style-rtl.css 4.39 kB
build/edit-widgets/style.css 4.39 kB
build/editor/index.min.js 38.4 kB
build/editor/style-rtl.css 3.71 kB
build/editor/style.css 3.71 kB
build/element/index.min.js 4.29 kB
build/escape-html/index.min.js 548 B
build/format-library/index.min.js 6.62 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.66 kB
build/html-entities/index.min.js 454 B
build/i18n/index.min.js 3.79 kB
build/is-shallow-equal/index.min.js 535 B
build/keyboard-shortcuts/index.min.js 1.83 kB
build/keycodes/index.min.js 1.41 kB
build/list-reusable-blocks/index.min.js 1.75 kB
build/list-reusable-blocks/style-rtl.css 838 B
build/list-reusable-blocks/style.css 838 B
build/media-utils/index.min.js 2.94 kB
build/notices/index.min.js 957 B
build/nux/index.min.js 2.12 kB
build/nux/style-rtl.css 751 B
build/nux/style.css 749 B
build/plugins/index.min.js 1.98 kB
build/preferences/index.min.js 1.2 kB
build/primitives/index.min.js 949 B
build/priority-queue/index.min.js 611 B
build/react-i18n/index.min.js 704 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.69 kB
build/reusable-blocks/index.min.js 2.24 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11.1 kB
build/server-side-render/index.min.js 1.61 kB
build/shortcode/index.min.js 1.52 kB
build/token-list/index.min.js 668 B
build/url/index.min.js 1.99 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 280 B
build/widgets/index.min.js 7.21 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.07 kB

compressed-size-action

Copy link
Contributor Author

@alexstine alexstine left a comment

Initial thoughts. It is much more accessible but this will need a ton of work I think.

@@ -190,7 +190,7 @@ function BlockSelectionButton( { clientId, rootClientId, blockElement } ) {
if ( focusedBlockUid ) {
event.preventDefault();
selectBlock( focusedBlockUid );
} else if ( isTab && selectedBlockClientId ) {
}/* else if ( isTab && selectedBlockClientId ) {
Copy link
Contributor Author

@alexstine alexstine Jan 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this line for? It seems to not be working properly. Even checked out on trunk this line is an issue. If I have one Paragraph and one Columns, I will enter Gutenberg Navigation Mode. If I press Tab, I will land on Paragraph, then Columns, then I get the first button within Columns. After commenting this line, I get placed in the Title field and I can go through my list again which seems like a much better workflow than random focus of Columns block content and possibly other Blocks.

Copy link
Contributor Author

@alexstine alexstine Jan 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to fix this in the below PR.

#37965

>
{ accessibleTitle }
</Button>
{ isAccessibleModalOpen && (
Copy link
Contributor Author

@alexstine alexstine Jan 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like this. I am duplicating too much code I think. Would be cool at some point to figure out how to pull this content in to the Modal via a ref or something. However, that may not be much more efficient.

Copy link
Member

@Mamaduka Mamaduka Jan 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, @alexstine

We could store __experimentalBlockVariationPicker into a variable then use it in both places. What do you think?

Example:

const variationPicker = (
  <__experimentalBlockVariationPicker
	  icon={ get( blockType, [ 'icon', 'src' ] ) }
	  label={ get( blockType, [ 'title' ] ) }
	  variations={ variations }
	  onSelect={ ( nextVariation = defaultVariation ) => {
		  if ( nextVariation.attributes ) {
			  setAttributes( nextVariation.attributes );
		  }
		  if ( nextVariation.innerBlocks ) {
			  replaceInnerBlocks(
				  clientId,
				  createBlocksFromInnerBlocksTemplate(
					  nextVariation.innerBlocks
				  ),
				  true
			  );
		  }
	  } }
	  allowSkip
  />
);

@alexstine alexstine requested review from nerrad and ntwb as code owners Jan 14, 2022
@MarcoZehe MarcoZehe closed this Jan 25, 2022
@MarcoZehe MarcoZehe reopened this Jan 25, 2022
@MarcoZehe
Copy link
Contributor

@MarcoZehe MarcoZehe commented Jan 25, 2022

Sorry about that.

Copy link
Contributor

@tellthemachines tellthemachines left a comment

Testing with VoiceOver on Safari, the dialog provides a much better experience than the default block placeholder. It does require markup duplication though, so it would be nicer if we could just make the existing placeholder markup more accessible.

Comparing the dialog and the placeholder markup, the main difference seems to be in the use of aria-labelledby and aria-describedby in the dialog, that causes the label and description to be read. The placeholder markup is totally devoid of semantics, and VoiceOver ignores it.

The other aspect is, of course, that the dialog keeps focus inside it so users can't tab away from the placeholder before selecting an option. How important is this? Because if we don't have to constrain focus to the placeholder, we can experiment with other types of markup, for instance, putting the whole placeholder inside a fieldset, with the label and description as its legend.

@alexstine
Copy link
Contributor Author

@alexstine alexstine commented Jan 28, 2022

@tellthemachines It was necessary to use the dialog because of the tabbing issues. Remember, for Windows users, Tab and Shift+Tab are primary navigation keys unlike the majority of cases on Mac with Voiceover. If there is some other approach that would allow us to get the same results of a dialog without using a dialog, that would be great. There are a ton of libraries at play in block-list though and it will be tough to figure out how to solve this without breaking it further. Essentially what needs to happen is this.

#37965

The only difference being we need to only focus the Sidebar when there is no more focusable element in the block content. This could get tricky though playing around with focus which is why I actually do like the dialog better but I understand how intrusive it is.

My thoughts:

  1. Insert a Columns Block.
  2. Columns Block is presented as an application instead of document.
  3. Add aria-labelledby for Columns title and aria-describedby for any needed descriptions.
  4. Focus is placed on first Block Variation.
  5. At this point, Shift+Tab should focus the Block Toolbar since there is no previous focusable element within the current content.
  6. Tab should continue through the Block Variations until it reaches the last focusable element in the content, then it should land on Block Sidebar.

Any of this sounding reasonable? It just sounds insane to have to mess with focus like this.

@tellthemachines
Copy link
Contributor

@tellthemachines tellthemachines commented Jan 31, 2022

It was necessary to use the dialog because of the tabbing issues. Remember, for Windows users, Tab and Shift+Tab are primary navigation keys unlike the majority of cases on Mac with Voiceover.

I'm not sure I've got this 100%, so to clarify: do we want to move focus into the block placeholder with the Tab key, when a new block is inserted? Currently, when inserting a Columns block, focus moves to the whole block, and then we have to press Down Arrow in order to access the buttons inside. But once inside, all the controls can be navigated with Tab.

Would focusing the first focusable element in the block after insertion solve the problem? Plus adding semantic markup so the block heading and description are read out, of course.

Whatever we decide on, the solution should work for all blocks that have placeholders: Navigation, Cover, Image, Gallery, Table, etc. so ideally it should be a global change in the placeholder, as opposed to individual changes for each block.

@alexstine
Copy link
Contributor Author

@alexstine alexstine commented Jan 31, 2022

@tellthemachines Oh no, I forgot to test this.

So, you are correct in one case.

  1. Insert a Columns Block.
  2. Down Arrow will focus the Block Variations.
  3. In theory, we should automatically focus the placeholder.

Here is the problem I am seeing.

  1. Select Options > Preferences.
  2. Select Blocks.
  3. Enable Contain text cursor inside block.
  4. Close the dialog and Options menu.
  5. Insert a Columns Block.
  6. Notice how pressing Down Arrow does not focus the Block Variations panel and how focus jumps directly to the Block Settings Sidebar.

Sorry for not being more clear about this, but it works differently based on that setting. We've got to have this working the same way across the board.

Thanks.

@alexstine
Copy link
Contributor Author

@alexstine alexstine commented Jan 31, 2022

It almost seems like Writing Flow is allowing some type of focus switching and whenever Writing Flow is disabled for switching from Block to Block, this is why it breaks.

@tellthemachines
Copy link
Contributor

@tellthemachines tellthemachines commented Jan 31, 2022

Oooh, I should have thought to test it with the "Contain text cursor inside block" setting! It makes sense that Arrow key navigation wouldn't work with that setting enabled.

I'm thinking ultimately the only way we can make things work properly is making the whole editor content navigable with Tab. But perhaps to start with, we can make it so the block placeholder buttons can be accessed with Tab instead of Down Arrow.

@alexstine alexstine changed the title POC: Try to make Columns Block accessible via dialog. Fix useFocusFirstElement in non-contenteditable Blocks Jan 31, 2022
@alexstine
Copy link
Contributor Author

@alexstine alexstine commented Jan 31, 2022

@tellthemachines I just pushed a fix which should help with this. Thoughts? I tested with Columns and Navigation Blocks and Tab works fine now. I also gave the Paragraph Block a test just to ensure this didn't break existing functionality. Finally, refreshed the PR with latest commits.

This was a much easier solution, wish I had thought of this earlier.

Thanks.

@alexstine alexstine force-pushed the try/accessible-block-content-dialog branch from 49592b6 to 065ba6f Compare Mar 5, 2022
if ( ! target.getAttribute( 'contenteditable' ) ) {
const focusElement = focus.tabbable.findNext( target );
// Ensure is not block inserter trigger, don't want to focus that in the event of the group block which doesn't contain any other focussable elements.
const skipBlockInserterTrigger = target.classList.contains(
Copy link
Contributor Author

@alexstine alexstine Mar 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been something of a terrible pain. For some reason, hasAttribute/getAttribute throw errors on tests so I gave up and just used classList. I attempted to get it working with aria-label but for whatever reason, this is way easier in tests than it is in practice.

@alexstine
Copy link
Contributor Author

@alexstine alexstine commented Mar 5, 2022

@ellatrix I have implemented the E2E tests. Does this help you understand what is going on?

BTW, this is the first couple E2E tests I've added so still learning... It is an interesting mindset to take a step back and mock how your functionality should work.

Copy link
Contributor

@andrewserong andrewserong left a comment

Thanks for all the great work here @alexstine! I'm not all that familiar with the focusing on first element behaviour, but this is testing nicely for me, and it does feel like a good improvement when inserting a Columns, Table, or Image block, and having focus be on the first button within the placeholder state.

My testing:

  • Before: when I insert an image block, the wrapper receives focus.
  • After: when I insert an image block, the Upload button receives focus.

The additional and updated e2e tests are appreciated, as it's quite a detailed interaction to get right! There was a good point made about it being harder to immediately delete a block after insertion, but I agree, I think there'll be ways to come up with a fix for that in a follow-up.

It'd be good to also get a final seal of approval from @ellatrix and / or @tellthemachines, but I just wanted to chime in that from my perspective I think this PR is in good shape! 🎉

@@ -192,6 +192,7 @@ describe( 'deleting all blocks', () => {
// Add and remove a block.
await insertBlock( 'Image' );
await page.waitForSelector( 'figure[data-type="core/image"]' );
await page.keyboard.press( 'ArrowUp' );
Copy link
Contributor

@andrewserong andrewserong Mar 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only relevant when you add a block, immediately realise it's the wrong block, and want to quickly delete it. It would be nice to preserve that ability, but we should be able to find a workaround for it.

I wonder if in a follow-up, we can look at in placeholder states that don't have an editable input field (e.g. the Columns placeholder state, but not the Twitter embed input field), and see if we can get backspace to still delete the block?

@ellatrix
Copy link
Member

@ellatrix ellatrix commented Mar 7, 2022

I'm mostly AFK until Thursday, but would like to review this before merge. I'll try to do it today or tomorrow or otherwise Thursday.

@alexstine
Copy link
Contributor Author

@alexstine alexstine commented Mar 9, 2022

@andrewserong In theory, it should be possible. The only thing I never liked about this method of removing a block is focus gets super lost so this may give us a good opportunity to fix both issues.

  1. Allow Backspace key to remove the block even if not on block wrapper for non-content editable blocks.
  2. On block removal, make sure focus is dropped in a place that makes sense instead of leaving screen reader users in the void.

Copy link
Member

@ellatrix ellatrix left a comment

To me this seems fine. I was considering a similar behaviour in #33494 and the code will have to be received as part of that PR. I generally agree that focus should move into a placeholder when it is inserted.

@alexstine alexstine force-pushed the try/accessible-block-content-dialog branch from 9d67e9f to f8871ca Compare Mar 11, 2022
let ariaLabel = await page.evaluate( () =>
document.activeElement.getAttribute( 'aria-label' )
);
// If the labels don't match, try pressing Up Arrow to focus the block wrapper in non-content editable block.
Copy link
Contributor Author

@alexstine alexstine Mar 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is way simpler compared to the modifications that were here and tests are still green. Seems logical to me. This whole file still is in desperate need of commenting.

Copy link
Contributor

@tellthemachines tellthemachines left a comment

This is working really well 🎉 - tested by adding Image, Table and Columns blocks. Also tested moving focus from the List View into those blocks, and duplicating an Image block from the List View. In all cases, focus lands on the first focusable element within the block placeholder.

Updates to existing tests look good, and thanks for adding some new tests for this behaviour!

@@ -192,6 +192,7 @@ describe( 'deleting all blocks', () => {
// Add and remove a block.
await insertBlock( 'Image' );
await page.waitForSelector( 'figure[data-type="core/image"]' );
await page.keyboard.press( 'ArrowUp' );
Copy link
Contributor

@tellthemachines tellthemachines Mar 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That won't work for Table though, because the first focusable field is an input 😅
But we should be able to work something out. Definitely as a follow-up.

@alexstine alexstine merged commit 0ef399d into trunk Mar 15, 2022
23 checks passed
@alexstine alexstine deleted the try/accessible-block-content-dialog branch Mar 15, 2022
@github-actions github-actions bot added this to the Gutenberg 12.9 milestone Mar 15, 2022
alefesouza added a commit to alefesouza/gutenberg that referenced this issue Mar 15, 2022
* Finally fix selector in test I think.

* If not contenteditable Block, find next focussable element.

* Try to make tests stable.

* Try to fix tests.

* Update snapshot.

* Fix focus getting stuck on child Block wrapper. Adjust to find only specific whitelisted element.

* Revert tests and start fresh.

* More work on tests.

* Add td to whitelisted elements. Check to see if first element has focus potential. Ensure the element to focus is within the current Block. Try to make tests stable.

* Revert some code.

* Back to test fixing.

* Try again to prevent focus in child Blocks. Tests should pass this time, maybe.

* Update another snapshot.

* More work on E2E tests.

* Cleanup code and try to fix more tests.

* Fix snapshot.

* Maybe fix another snapshot.

* Make sure returning early doesn't prevent false positive of still having a chance to focus.

* Update snapshots.

* Fix test snapshot.

* Update tests with focus order changes.

* Make the implementation more stable. Fix the List View focus bug.

* Use target everywhere. Add a check for Block Inserter trigger.

* Use Tab to exit the toolbar so blocks could be navigated. Only seems to be a partial fix for this E2E test failure.

* Fix failing test and update snapshot.

* Fix group block still places focus on Add block button. Add some E2E tests.

* Fix code comment.

* Fix bugs.

* Formatting fixes.

* Update to use class name.

* Try again to fix my own test.

* Try again to rewrite toolbar roving tabindex now that E2E is working.

* Better code commenting.

Co-authored-by: tellthemachines <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment