-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Escape on Block Toolbar returns focus to Editor Canvas #55712
Conversation
Size Change: +264 B (0%) Total Size: 1.7 MB
ℹ️ View Unchanged
|
Flaky tests detected in 18f4f14. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6698846222
|
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 looking good. I am seeing a unconnected issue where when I have a cursor inside a block (like a paragraph or a heading) alt+F10 isn't taking me to the toolbar, but this also happens in trunk. It might just be that I'm not very good at keyboard navigation!
Stores the element that had last focus from the editor when focus leaves the editor canvas
Adding the escape keypress from the navigable block tools will require forwarding the ref from the BlockTools all the way down to the NavigableToolbar.
This method requires forwarding a ref from the editor level down to the navigable toolbar so that the escape unselect shortcut can be blocked and the navigable toolbar event listener will still fire. Blocking the global escape event shouldn't be necessary, but we have a combination of a few things all combining to create a situation where: - Because the block toolbar uses createPortal to populate the block toolbar fills, we can't rely on the React event bubbling to hit the onKeyDown listener for the block toolbar - Since we can't use the React tree, we use the DOM tree which _should_ handle the event bubbling correctly from a `createPortal` element. - This bubbles via the React tree, which hits this `unselect` escape keypress before the block toolbar DOM event listener has access to it. Also, this is better than attaching it to all of the children in the block toolbar because when new items are attached to the toolbar (such as via the bubblesVirtually slots or the apply/cancel buttons when cropping an image) we can still catch the events. Otherwise, those buttons are added after the mount, and the children don't receive the listener.
I added it since it was passed in, but it is a callback and doesn't change. It was causing unnecssary rerenders that was messing with the focus position when selecting an item from a dropdown, such as when changing the heading level on a site title block.
dbfb084
to
ad87967
Compare
Since there is already agreement on this approach I think we should bring it in. |
08ec8dd
to
8558e69
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.
One naming nit about all the new components which forward refs to, the "unforward" makes things look more complicated. I'd go for not using the prefix at all or chaning it to unforwarding or ed or something 😁
@@ -31,7 +32,10 @@ import BlockToolbar from '../block-toolbar'; | |||
import { store as blockEditorStore } from '../../store'; | |||
import { useHasAnyBlockControls } from '../block-controls/use-has-block-controls'; | |||
|
|||
function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) { | |||
function UnforwardBlockContextualToolbar( |
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 name confuses me UnforwardBlockContextualToolbar
. Whati Unforward for? Maybe ... Unforwarded?
return null; | ||
} | ||
|
||
const SelectedBlockPopover = forwardRef( UnforwardSelectedBlockPopover ); |
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.
Same comment, I think adding an -ed suffix makes it better :)
export function lastFocus( state = false, action ) { | ||
switch ( action.type ) { | ||
case 'LAST_FOCUS': | ||
return action.lastFocus; |
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.
Storing unserializable values in Redux's store breaks the compatibility of Redux Devtools. It will just output t.toString() is not a function
whenever it tries to serialize it and provide no helpful trace 😅.
What's worse is that it could just crash the page unexpectedly, without the Redux Devtools even being active (it just has to be initialized).
Related: #57198 (comment) and #57257 (comment).
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.
Thank you for tracking this down! Got an approach for review here: #57557
@@ -588,6 +588,18 @@ _Properties_ | |||
- _isDisabled_ `boolean`: Whether or not the user should be prevented from inserting this item. | |||
- _frecency_ `number`: Heuristic that combines frequency and recency. | |||
|
|||
### getLastFocus |
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 we avoid this being a public API please. (and the action as well)
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.
✅ #57557
What?
Adds using escape keypress to return from toolbar to editor canvas. Right now the escape keypress on the toolbar causes the blocks to be deselected and loses focus. Escape from a floating element is a very common pattern for returning focus to the canvas -- moreso than
tab
.Why?
This is part of the Top Toolbar improvement work. This adds a real feature to Gutenberg that can be expanded upon, and will make it easier to merge #54513 instead of reviewing 70 commits...
How?
Storing
lastFocus
element when focus leaves the block toolbarTo help make managing focus easier across Gutenberg, anytime focus leaves the editor canvas, the element that previously had focus will be stored. This will help fix focus loss as well, as this PR uses the same methods: #54443.
Catching Escape Keypress from Block Toolbar
@ajlende and I investigated a lot of ways of catching the escape keypress from the navigable toolbar, and landed with three viable options. The selected method requires forwarding a ref from the editor level down to the navigable toolbar so that the escape unselect shortcut can be blocked and the navigable toolbar event listener will still fire.
Blocking the global escape event shouldn't be necessary, but we have a combination of a few things all combining to create a situation where:
createPortal
to populate the block toolbar fills, we can't rely on the React event bubbling to hit theonKeyDown
listener for the block toolbarcreatePortal
element.unselect
escape keypress before the block toolbar DOM event listener has access to it.Also, this is better than attaching it to all of the children in the block toolbar because when new items are dynamically rendered into the toolbar (such as via the
bubblesVirtually
slots or the apply/cancel buttons when cropping an image) we can still catch the events. Otherwise, those buttons are added after the mount, and the children don't receive the listener.Testing Instructions for Keyboard
shift + tab
to move focus to the block toolbarescape
to return focus to the block