diff --git a/packages/block-editor/src/components/block-list-appender/index.js b/packages/block-editor/src/components/block-list-appender/index.js index ddc22b3b928bbf..94cc4505dd0b01 100644 --- a/packages/block-editor/src/components/block-list-appender/index.js +++ b/packages/block-editor/src/components/block-list-appender/index.js @@ -23,49 +23,54 @@ function BlockListAppender( { isLocked, renderAppender: CustomAppender, } ) { - if ( isLocked ) { + if ( isLocked || CustomAppender === false ) { return null; } - // If a render prop has been provided - // use it to render the appender. + let appender; if ( CustomAppender ) { - return ( -
- -
- ); - } - - // a false value means, don't render any appender. - if ( CustomAppender === false ) { - return null; - } - - // Render the default block appender when renderAppender has not been - // provided and the context supports use of the default appender. - if ( canInsertDefaultBlock ) { - return ( -
- - - -
+ // Prefer custom render prop if provided. + appender = ; + } else if ( canInsertDefaultBlock ) { + // Render the default block appender when renderAppender has not been + // provided and the context supports use of the default appender. + appender = ( + ); - } - - // Fallback in the case no renderAppender has been provided and the - // default block can't be inserted. - return ( -
+ } else { + // Fallback in the case no renderAppender has been provided and the + // default block can't be inserted. + appender = ( -
+ ); + } + + // IgnoreNestedEvents is used to treat interactions within the appender as + // subject to the same conditions as those which occur within nested blocks. + // Notably, this effectively prevents event bubbling to block ancestors + // which can otherwise interfere with the intended behavior of the appender + // (e.g. focus handler on the ancestor block). + // + // A `tabIndex` is used on the wrapping `div` element in order to force a + // focus event to occur when an appender `button` element is clicked. In + // some browsers (Firefox, Safari), button clicks do not emit a focus event, + // which could cause this event to propagate unexpectedly. The `tabIndex` + // ensures that the interaction is captured as a focus, without also adding + // an extra tab stop. + // + // See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus + return ( + +
+ { appender } +
+
); } diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index a505e536d847d3..4d876593faa02a 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -290,7 +290,7 @@ function BlockListBlock( { * (via `setFocus`), typically if there is no focusable input in the block. */ const onFocus = () => { - if ( ! isSelected && ! isAncestorOfSelectedBlock && ! isPartOfMultiSelection ) { + if ( ! isSelected && ! isPartOfMultiSelection ) { onSelect(); } }; diff --git a/packages/block-editor/src/components/inner-blocks/default-block-appender.js b/packages/block-editor/src/components/inner-blocks/default-block-appender.js index 1aa6dbc8548b89..43a98be106a2b3 100644 --- a/packages/block-editor/src/components/inner-blocks/default-block-appender.js +++ b/packages/block-editor/src/components/inner-blocks/default-block-appender.js @@ -12,18 +12,15 @@ import { withSelect } from '@wordpress/data'; /** * Internal dependencies */ -import IgnoreNestedEvents from '../ignore-nested-events'; import BaseDefaultBlockAppender from '../default-block-appender'; import withClientId from './with-client-id'; export const DefaultBlockAppender = ( { clientId, lastBlockClientId } ) => { return ( - - - + ); }; diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index 2db998bb768e17..c01171106e3372 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -33,10 +33,6 @@ const defaultRenderToggle = ( { onToggle, disabled, isOpen, blockTitle, hasSingl icon="insert" label={ label } labelPosition="bottom" - onMouseDown={ ( event ) => { - event.preventDefault(); - event.currentTarget.focus(); - } } onClick={ onToggle } className="block-editor-inserter__toggle" aria-haspopup={ ! hasSingleBlockType ? 'true' : false } diff --git a/packages/e2e-tests/specs/editor/various/writing-flow.test.js b/packages/e2e-tests/specs/editor/various/writing-flow.test.js index f0683ff51e0f09..661267778b6cad 100644 --- a/packages/e2e-tests/specs/editor/various/writing-flow.test.js +++ b/packages/e2e-tests/specs/editor/various/writing-flow.test.js @@ -10,6 +10,8 @@ import { insertBlock, } from '@wordpress/e2e-test-utils'; +const getActiveBlockName = async () => page.evaluate( () => wp.data.select( 'core/block-editor' ).getSelectedBlock().name ); + describe( 'Writing Flow', () => { beforeEach( async () => { await createNewPost(); @@ -20,7 +22,11 @@ describe( 'Writing Flow', () => { // not be necessary for interactions, and exist as a stop-gap solution // where rendering delays in slower CPU can cause intermittent failure. - let activeElementText; + // Assertions are made both against the active DOM element and the + // editor state, in order to protect against potential disparities. + // + // See: https://github.com/WordPress/gutenberg/issues/18928 + let activeElementText, activeBlockName; // Add demo content await clickBlockAppender(); @@ -54,13 +60,19 @@ describe( 'Writing Flow', () => { // Arrow up into nested context focuses last text input await page.keyboard.press( 'ArrowUp' ); + activeBlockName = await getActiveBlockName(); + expect( activeBlockName ).toBe( 'core/paragraph' ); activeElementText = await page.evaluate( () => document.activeElement.textContent ); expect( activeElementText ).toBe( '2nd col' ); // Arrow up in inner blocks should navigate through (1) column wrapper, // (2) text fields. await page.keyboard.press( 'ArrowUp' ); + activeBlockName = await getActiveBlockName(); + expect( activeBlockName ).toBe( 'core/column' ); await page.keyboard.press( 'ArrowUp' ); + activeBlockName = await getActiveBlockName(); + expect( activeBlockName ).toBe( 'core/paragraph' ); activeElementText = await page.evaluate( () => document.activeElement.textContent ); expect( activeElementText ).toBe( '1st col' ); @@ -72,15 +84,21 @@ describe( 'Writing Flow', () => { document.activeElement.getAttribute( 'data-type' ) ) ); expect( activeElementBlockType ).toBe( 'core/column' ); + activeBlockName = await getActiveBlockName(); + expect( activeBlockName ).toBe( 'core/column' ); await page.keyboard.press( 'ArrowUp' ); activeElementBlockType = await page.evaluate( () => ( document.activeElement.getAttribute( 'data-type' ) ) ); expect( activeElementBlockType ).toBe( 'core/columns' ); + activeBlockName = await getActiveBlockName(); + expect( activeBlockName ).toBe( 'core/columns' ); // Arrow up from focused (columns) block wrapper exits nested context // to prior text input. await page.keyboard.press( 'ArrowUp' ); + activeBlockName = await getActiveBlockName(); + expect( activeBlockName ).toBe( 'core/paragraph' ); activeElementText = await page.evaluate( () => document.activeElement.textContent ); expect( activeElementText ).toBe( 'First paragraph' );