-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Fix useFocusFirstElement in non-contenteditable Blocks #37934
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
Changes from 34 commits
160067a
1251408
e735aa3
3c6311d
f18270d
8f352e8
b947e8c
6ee7747
818115e
21e15dd
e3f373b
038d1b8
be095b8
fbffdf5
c421834
5007b54
cc894c4
643e470
df7bf85
51d8e7a
316b9cc
8969d23
ce52e45
01e0210
c0027c4
41ffcec
cdea00f
855bae1
4ac00b0
72a5933
065ba6f
53b7cd5
c2a8ba9
300c302
b66fbd2
f8871ca
17f1dd0
3f4df14
948f325
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,6 +52,16 @@ function useInitialPosition( clientId ) { | |
| ); | ||
| } | ||
|
|
||
| function isFormElement( element ) { | ||
| const { tagName } = element; | ||
| return ( | ||
| tagName === 'INPUT' || | ||
| tagName === 'BUTTON' || | ||
| tagName === 'SELECT' || | ||
| tagName === 'TEXTAREA' | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Transitions focus to the block or inner tabbable when the block becomes | ||
| * selected and an initial position is set. | ||
|
|
@@ -96,6 +106,25 @@ export function useFocusFirstElement( clientId ) { | |
| return; | ||
| } | ||
|
|
||
| // Check to see if element is focussable before a generic caret insert. | ||
| 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( | ||
|
||
| 'block-editor-button-block-appender' | ||
| ); | ||
| // Make sure focusElement is valid, form field, and in current ref. | ||
| if ( | ||
| focusElement && | ||
| isFormElement( focusElement ) && | ||
| target.contains( focusElement ) && | ||
| ! skipBlockInserterTrigger | ||
| ) { | ||
| focusElement.focus(); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| setContentEditableWrapper( ref.current, false ); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ellatrix I am not sure what this line is for right off hand, is it okay to have my code run before this or do I need to switch the order? |
||
|
|
||
| placeCaretAtHorizontalEdge( target, isReverse ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -106,6 +106,7 @@ function ListViewBlock( { | |
| const selectEditorBlock = useCallback( | ||
| ( event ) => { | ||
| selectBlock( event, clientId ); | ||
| event.preventDefault(); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Had to add this because if focus was placed on a button, the button would get clicked causing an unprovoked action. E.g.
|
||
| }, | ||
| [ clientId, selectBlock ] | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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' ); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that Arrow Up to select the block wrapper won't work if 'Contain text cursor inside block' is enabled, so it will be much harder to delete a block immediately after insertion.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tellthemachines This didn't change though, right? If contains cursor option is selected, Up Arrow on latest trunk should not focus the wrapper either.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, but with these changes the wrapper won't be focused after insertion anymore. 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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 😅 |
||
| await page.keyboard.press( 'Backspace' ); | ||
|
|
||
| // Verify there is no selected block. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,18 +20,16 @@ async function expectLabelToHaveFocus( label ) { | |
| ).toBe( label ); | ||
| } | ||
|
|
||
| async function testBlockToolbarKeyboardNavigation( | ||
| currentBlockLabel, | ||
| currentBlockTitle | ||
| ) { | ||
| async function testBlockToolbarKeyboardNavigation( currentBlockTitle ) { | ||
| await focusBlockToolbar(); | ||
| await expectLabelToHaveFocus( currentBlockTitle ); | ||
| await page.keyboard.press( 'ArrowRight' ); | ||
| await expectLabelToHaveFocus( 'Move up' ); | ||
| await page.keyboard.press( 'Tab' ); | ||
| await expectLabelToHaveFocus( currentBlockLabel ); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it a problem that we're now unable to focus the block wrapper on a new block? Since it has a "document" role I'd expect it to still be focusable. The other thing to consider is we're introducing a change in how focus behaves only for newly-added blocks, so supposing we add a block, then move focus to another block or another part of the page, and then move back again, the behaviour will be different. This might create confusion, e.g. "why am I now able to focus the block wrapper when I wasn't before?"
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tellthemachines In my testing, the block wrapper can still gain focus if you use Up Arrow from an element. Not sure why it doesn't work in some tests.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we have to remove this check? Can we still test that moving focus from the toolbar to the content is still possible?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ellatrix I changed this because landing on the first element inside the wrapper is no longer the aria-label of the block wrapper so the test failed. I tried to still ensure moving focus to toolbar and back worked but if you press Tab from toolbar, wrapper is skipped.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ellatrix Rewrote the test to be less confusing. Hopefully better structure and comment helps. This test is lacking on comments as it is. Something to improve in the future.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another update. I could never make the tests pass. Is it fine to try to improve the tests in a follow-up PR? Essentially I need to implement some type of function for content editable blocks vs. non-content editable blocks. Content editable blocks can have the aria-label parsed directly but non-content editable blocks cannot since Up Arrow is now required to focus the block wrapper. In my opinion, these tests are still good since it checks to see if the toolbar is still accessible via Shift+Tab and Arrow keys. This PR is getting far too big for me to keep good track of as well so I'd like to split this off soon.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I think my E2E tests were fine but trunk is currently failing. 👎 I wish I would have checked that sooner. I wondered why they were passing locally... 😕 |
||
| await pressKeyWithModifier( 'shift', 'Tab' ); | ||
| await expectLabelToHaveFocus( 'Move up' ); | ||
| await page.keyboard.press( 'ArrowLeft' ); | ||
| await expectLabelToHaveFocus( currentBlockTitle ); | ||
| } | ||
|
|
||
| async function wrapCurrentBlockWithGroup( currentBlockTitle ) { | ||
|
|
@@ -64,53 +62,50 @@ describe( 'Toolbar roving tabindex', () => { | |
| it( 'ensures paragraph block toolbar uses roving tabindex', async () => { | ||
| await insertBlock( 'Paragraph' ); | ||
| await page.keyboard.type( 'Paragraph' ); | ||
| await testBlockToolbarKeyboardNavigation( | ||
| 'Paragraph block', | ||
| 'Paragraph' | ||
| ); | ||
| await testBlockToolbarKeyboardNavigation( 'Paragraph' ); | ||
| await wrapCurrentBlockWithGroup( 'Paragraph' ); | ||
| await testGroupKeyboardNavigation( 'Paragraph block', 'Paragraph' ); | ||
| } ); | ||
|
|
||
| it( 'ensures heading block toolbar uses roving tabindex', async () => { | ||
| await insertBlock( 'Heading' ); | ||
| await page.keyboard.type( 'Heading' ); | ||
| await testBlockToolbarKeyboardNavigation( 'Block: Heading', 'Heading' ); | ||
| await testBlockToolbarKeyboardNavigation( 'Heading' ); | ||
| await wrapCurrentBlockWithGroup( 'Heading' ); | ||
| await testGroupKeyboardNavigation( 'Block: Heading', 'Heading' ); | ||
| } ); | ||
|
|
||
| it( 'ensures list block toolbar uses roving tabindex', async () => { | ||
| await insertBlock( 'List' ); | ||
| await page.keyboard.type( 'List' ); | ||
| await testBlockToolbarKeyboardNavigation( 'Block: List', 'List' ); | ||
| await testBlockToolbarKeyboardNavigation( 'List' ); | ||
| await wrapCurrentBlockWithGroup( 'List' ); | ||
| await testGroupKeyboardNavigation( 'Block: List', 'List' ); | ||
| } ); | ||
|
|
||
| it( 'ensures image block toolbar uses roving tabindex', async () => { | ||
| await insertBlock( 'Image' ); | ||
| await testBlockToolbarKeyboardNavigation( 'Block: Image', 'Image' ); | ||
| await testBlockToolbarKeyboardNavigation( 'Image' ); | ||
| await wrapCurrentBlockWithGroup( 'Image' ); | ||
| await testGroupKeyboardNavigation( 'Block: Image', 'Image' ); | ||
| } ); | ||
|
|
||
| it( 'ensures table block toolbar uses roving tabindex', async () => { | ||
| await insertBlock( 'Table' ); | ||
| await testBlockToolbarKeyboardNavigation( 'Block: Table', 'Table' ); | ||
| await testBlockToolbarKeyboardNavigation( 'Table' ); | ||
| // Move focus to the first toolbar item. | ||
| await page.keyboard.press( 'Home' ); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does pressing the 'Home' key no longer take us to the beginning of the toolbar? I'm not able to test this PR on Windows as gutenberg.run doesn't seem to be working.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tellthemachines It did in my testing. That part should not be effected. The only things I changed around this were taking in to account not reading the aria-label from the block wrapper as focus was skipped. |
||
| await expectLabelToHaveFocus( 'Table' ); | ||
| await page.click( '.blocks-table__placeholder-button' ); | ||
| await page.keyboard.press( 'Tab' ); | ||
| await testBlockToolbarKeyboardNavigation( 'Body cell text', 'Table' ); | ||
| await expectLabelToHaveFocus( 'Body cell text' ); | ||
| await wrapCurrentBlockWithGroup( 'Table' ); | ||
| await testGroupKeyboardNavigation( 'Block: Table', 'Table' ); | ||
| } ); | ||
|
|
||
| it( 'ensures custom html block toolbar uses roving tabindex', async () => { | ||
| await insertBlock( 'Custom HTML' ); | ||
| await testBlockToolbarKeyboardNavigation( 'HTML', 'Custom HTML' ); | ||
| await testBlockToolbarKeyboardNavigation( 'Custom HTML' ); | ||
| await wrapCurrentBlockWithGroup( 'Custom HTML' ); | ||
| await testGroupKeyboardNavigation( | ||
| 'Block: Custom HTML', | ||
|
|
||
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 borrowed this function from writing-flow. Should probably throw this somewhere else in the future.