diff --git a/packages/block-editor/src/components/block-popover/inbetween.js b/packages/block-editor/src/components/block-popover/inbetween.js index 6bea16e9cd5bdf..6c0d9d034f0a2d 100644 --- a/packages/block-editor/src/components/block-popover/inbetween.js +++ b/packages/block-editor/src/components/block-popover/inbetween.js @@ -179,7 +179,7 @@ function BlockPopoverInbetween( { key={ nextClientId + '--' + rootClientId } { ...props } className={ classnames( - 'block-editor-block-popover', + 'block-editor-block-popover block-editor-block-popover-in-between', props.className ) } __unstableForcePosition diff --git a/packages/block-editor/src/components/block-tools/insertion-point.js b/packages/block-editor/src/components/block-tools/insertion-point.js index 96e3fb1043d71a..1629b27ef17b04 100644 --- a/packages/block-editor/src/components/block-tools/insertion-point.js +++ b/packages/block-editor/src/components/block-tools/insertion-point.js @@ -24,7 +24,7 @@ function InsertionPointPopover( { __unstablePopoverSlot, __unstableContentRef, } ) { - const { selectBlock, hideInsertionPoint } = useDispatch( blockEditorStore ); + const { hideInsertionPoint } = useDispatch( blockEditorStore ); const openRef = useContext( InsertionPointOpenRef ); const ref = useRef(); const { @@ -74,12 +74,6 @@ function InsertionPointPopover( { const disableMotion = useReducedMotion(); - function onClick( event ) { - if ( event.target === ref.current && nextClientId ) { - selectBlock( nextClientId, -1 ); - } - } - function onFocus( event ) { // Only handle click on the wrapper specifically, and not an event // bubbled from the inserter itself. @@ -190,11 +184,8 @@ function InsertionPointPopover( { exit="start" ref={ ref } tabIndex={ -1 } - onClick={ onClick } onFocus={ onFocus } - className={ classnames( className, { - 'is-with-inserter': isInserterShown, - } ) } + className={ className } onHoverEnd={ maybeHideInserterPoint } > * { + pointer-events: none; +} + .block-editor-block-list__insertion-point { position: absolute; top: 0; @@ -29,6 +34,7 @@ // This is the clickable plus. .block-editor-block-list__insertion-point-inserter { + pointer-events: all; // Don't show on mobile. display: none; position: absolute; diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index dfd44e828c7f8f..aecaa479b46c6f 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -21,6 +21,7 @@ import useSelectAll from './use-select-all'; import useDragSelection from './use-drag-selection'; import useSelectionObserver from './use-selection-observer'; import useClickSelection from './use-click-selection'; +import useClickRedirect from './use-click-redirect'; import useInput from './use-input'; import { store as blockEditorStore } from '../../store'; @@ -39,6 +40,7 @@ export function useWritingFlow() { useDragSelection(), useSelectionObserver(), useClickSelection(), + useClickRedirect(), useMultiSelection(), useSelectAll(), useArrowNav(), diff --git a/packages/block-editor/src/components/writing-flow/use-click-redirect.js b/packages/block-editor/src/components/writing-flow/use-click-redirect.js new file mode 100644 index 00000000000000..d98865d10b71cf --- /dev/null +++ b/packages/block-editor/src/components/writing-flow/use-click-redirect.js @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { useRefEffect } from '@wordpress/compose'; + +export default function useClickSelection() { + return useRefEffect( ( node ) => { + function onMouseDown( event ) { + if ( + ! event.target.classList.contains( + 'block-editor-block-list__layout' + ) + ) { + return; + } + + node.contentEditable = true; + // Firefox doesn't automatically move focus. + node.focus(); + // The selection observer will turn off contentEditable after a + // selection change. + } + + node.addEventListener( 'mousedown', onMouseDown ); + + return () => { + node.removeEventListener( 'mousedown', onMouseDown ); + }; + }, [] ); +} diff --git a/packages/block-editor/src/components/writing-flow/use-selection-observer.js b/packages/block-editor/src/components/writing-flow/use-selection-observer.js index bb96f19a996b99..797414d53dacc2 100644 --- a/packages/block-editor/src/components/writing-flow/use-selection-observer.js +++ b/packages/block-editor/src/components/writing-flow/use-selection-observer.js @@ -97,7 +97,40 @@ export default function useSelectionObserver() { // For now we check if the event is a `mouse` event. const isClickShift = event.shiftKey && event.type === 'mouseup'; if ( selection.isCollapsed && ! isClickShift ) { + // Only process if selection is enabled on the whole editor. + if ( node.getAttribute( 'contenteditable' ) === 'false' ) { + return; + } + + let selectedNode = extractSelectionStartNode( selection ); + setContentEditableWrapper( node, false ); + + const { activeElement } = ownerDocument; + + // No selection. + if ( ! selectedNode ) return; + // Already focussed. + if ( selectedNode === activeElement ) return; + // Wrapper is focussed. + if ( node === activeElement ) return; + // Focus lies outside the editor. + if ( ! node.contains( activeElement ) ) return; + + // Ensure that the selection is an element. + if ( selectedNode.nodeType !== selectedNode.ELEMENT_NODE ) { + selectedNode = selectedNode.parentElement; + } + + // Find the closest focusable element. + selectedNode = selectedNode.closest( '[tabindex]' ); + + // Focus shouldn't be moved outside the editor. + if ( ! node.contains( selectedNode ) ) return; + // And focus shouldn't be moved to the editor's wrapper. + if ( selectedNode === node ) return; + + selectedNode.focus(); return; } diff --git a/packages/e2e-test-utils-playwright/src/request-utils/themes.ts b/packages/e2e-test-utils-playwright/src/request-utils/themes.ts index bb2fbd89781595..01f5f4949656b6 100644 --- a/packages/e2e-test-utils-playwright/src/request-utils/themes.ts +++ b/packages/e2e-test-utils-playwright/src/request-utils/themes.ts @@ -4,7 +4,7 @@ import type { RequestUtils } from './index'; import { WP_BASE_URL } from '../config'; -const THEMES_URL = new URL( '/wp-admin/themes.php', WP_BASE_URL ).href; +const THEMES_URL = new URL( 'wp-admin/themes.php', WP_BASE_URL ).href; async function activateTheme( this: RequestUtils, diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap index 37328170c07e11..d297b4ffb8df4a 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap @@ -258,13 +258,29 @@ exports[`Writing Flow should not have a dead zone above an aligned block 1`] = ` " `; +exports[`Writing Flow should not have a dead zone abover separator 1`] = ` +" +

1

+ + + +
+" +`; + +exports[`Writing Flow should not have a dead zone abover separator 2`] = ` +" +

1

+" +`; + exports[`Writing Flow should not have a dead zone between blocks (lower) 1`] = ` "

1

-

23

+

32

" `; 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 322f64f0fcffed..583643f2c154eb 100644 --- a/packages/e2e-tests/specs/editor/various/writing-flow.test.js +++ b/packages/e2e-tests/specs/editor/various/writing-flow.test.js @@ -613,11 +613,41 @@ describe( 'Writing Flow', () => { const lowerInserterY = inserterRect.y + ( 2 * inserterRect.height ) / 3; await page.mouse.click( x, lowerInserterY ); + await page.waitForFunction( + () => window.getSelection().type === 'Caret' + ); await page.keyboard.type( '3' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + it( 'should not have a dead zone abover separator', async () => { + await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '---' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.type( '1' ); + + // Test setup: "1" + separator. + expect( await getEditedPostContent() ).toMatchSnapshot(); + + // Find a point outside the paragraph between the blocks where it's + // expected that the sibling inserter would be placed. + const paragraph = await page.$( '[data-type="core/paragraph"]' ); + const paragraphRect = await paragraph.boundingBox(); + const x = paragraphRect.x + ( 2 * paragraphRect.width ) / 3; + const y = paragraphRect.y + paragraphRect.height + 1; + + await page.mouse.click( x, y ); + await page.waitForFunction( + () => window.getSelection().type === 'Caret' + ); + await page.keyboard.press( 'Backspace' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + it( 'should not have a dead zone above an aligned block', async () => { await page.keyboard.press( 'Enter' ); await page.keyboard.type( '1' );