From 05f29309ba153dd9251f2fd473459c56b0733344 Mon Sep 17 00:00:00 2001 From: Ella Date: Fri, 12 Jul 2024 14:40:26 +0200 Subject: [PATCH 01/11] Writing flow: select next block on Enter key --- .../use-selected-block-event-handlers.js | 60 +++++++++++++++++-- packages/block-library/src/site-title/edit.js | 10 +--- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js index 0a13ce6700b8e8..bcf05eadc0daf2 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js @@ -1,12 +1,11 @@ /** * WordPress dependencies */ -import { isTextField } from '@wordpress/dom'; import { ENTER, BACKSPACE, DELETE } from '@wordpress/keycodes'; import { useSelect, useDispatch } from '@wordpress/data'; import { useRefEffect } from '@wordpress/compose'; import { createRoot } from '@wordpress/element'; -import { store as blocksStore } from '@wordpress/blocks'; +import { store as blocksStore, getDefaultBlockName } from '@wordpress/blocks'; /** * Internal dependencies @@ -25,14 +24,23 @@ import BlockDraggableChip from '../../../components/block-draggable/draggable-ch */ export function useEventHandlers( { clientId, isSelected } ) { const { getBlockType } = useSelect( blocksStore ); - const { getBlockRootClientId, isZoomOut, hasMultiSelection, getBlockName } = - unlock( useSelect( blockEditorStore ) ); + const { + getBlockRootClientId, + isZoomOut, + hasMultiSelection, + getBlockName, + canInsertBlockType, + getNextBlockClientId, + getBlockOrder, + getBlockEditingMode, + } = unlock( useSelect( blockEditorStore ) ); const { insertAfterBlock, removeBlock, resetZoomLevel, startDraggingBlocks, stopDraggingBlocks, + selectBlock, } = unlock( useDispatch( blockEditorStore ) ); return useRefEffect( @@ -53,6 +61,10 @@ export function useEventHandlers( { clientId, isSelected } ) { function onKeyDown( event ) { const { keyCode, target } = event; + if ( event.defaultPrevented ) { + return; + } + if ( keyCode !== ENTER && keyCode !== BACKSPACE && @@ -61,7 +73,7 @@ export function useEventHandlers( { clientId, isSelected } ) { return; } - if ( target !== node || isTextField( target ) ) { + if ( target !== node ) { return; } @@ -70,7 +82,43 @@ export function useEventHandlers( { clientId, isSelected } ) { if ( keyCode === ENTER && isZoomOut() ) { resetZoomLevel(); } else if ( keyCode === ENTER ) { - insertAfterBlock( clientId ); + const rootClientId = getBlockRootClientId( clientId ); + if ( + canInsertBlockType( + getDefaultBlockName(), + rootClientId + ) + ) { + insertAfterBlock( clientId ); + } else { + function getNextClientId( id ) { + let nextClientId = null; + + while ( + typeof id === 'string' && + ! ( nextClientId = getNextBlockClientId( id ) ) + ) { + id = getBlockRootClientId( id ); + } + + return nextClientId; + } + + let nextClientId = + getBlockOrder( clientId )[ 0 ] ?? + getNextClientId( clientId ); + + while ( + nextClientId && + getBlockEditingMode( nextClientId ) === 'disabled' + ) { + nextClientId = getNextClientId( nextClientId ); + } + + if ( nextClientId ) { + selectBlock( nextClientId ); + } + } } else { removeBlock( clientId ); } diff --git a/packages/block-library/src/site-title/edit.js b/packages/block-library/src/site-title/edit.js index 644629a96fe4e1..c6781bc2d9ddf8 100644 --- a/packages/block-library/src/site-title/edit.js +++ b/packages/block-library/src/site-title/edit.js @@ -22,14 +22,9 @@ import { __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem, } from '@wordpress/components'; -import { createBlock, getDefaultBlockName } from '@wordpress/blocks'; import { decodeEntities } from '@wordpress/html-entities'; -export default function SiteTitleEdit( { - attributes, - setAttributes, - insertBlocksAfter, -} ) { +export default function SiteTitleEdit( { attributes, setAttributes } ) { const { level, levelOptions, textAlign, isLink, linkTarget } = attributes; const { canUserEdit, title } = useSelect( ( select ) => { const { canUser, getEntityRecord, getEditedEntityRecord } = @@ -72,9 +67,6 @@ export default function SiteTitleEdit( { onChange={ setTitle } allowedFormats={ [] } disableLineBreaks - __unstableOnSplitAtEnd={ () => - insertBlocksAfter( createBlock( getDefaultBlockName() ) ) - } /> ) : ( From 17ebe1839a6cc4bbacee7f6e6678d85741699eb0 Mon Sep 17 00:00:00 2001 From: Ella Date: Wed, 4 Dec 2024 10:12:24 +0100 Subject: [PATCH 02/11] Select the block wrapper --- .../use-block-props/use-focus-first-element.js | 9 +++++---- .../use-block-props/use-selected-block-event-handlers.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js b/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js index 26f7cca2990d89..40262c99e19ddf 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js @@ -43,10 +43,6 @@ export function useFocusFirstElement( { clientId, initialPosition } ) { return; } - if ( initialPosition === undefined || initialPosition === null ) { - return; - } - if ( ! ref.current ) { return; } @@ -58,6 +54,11 @@ export function useFocusFirstElement( { clientId, initialPosition } ) { return; } + if ( initialPosition === undefined || initialPosition === null ) { + ref.current.focus(); + return; + } + // Find all tabbables within node. const textInputs = focus.tabbable .find( ref.current ) diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js index bcf05eadc0daf2..be666dc12d34af 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js @@ -116,7 +116,7 @@ export function useEventHandlers( { clientId, isSelected } ) { } if ( nextClientId ) { - selectBlock( nextClientId ); + selectBlock( nextClientId, null ); } } } else { From febd0d5276772bb7d9affe85995efc354bf8e827 Mon Sep 17 00:00:00 2001 From: Ella Date: Wed, 4 Dec 2024 15:51:59 +0100 Subject: [PATCH 03/11] Adjust for splitting refactor --- .../use-selected-block-event-handlers.js | 61 +++------------- .../src/components/writing-flow/use-input.js | 69 +++++++++++++++---- 2 files changed, 65 insertions(+), 65 deletions(-) diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js index be666dc12d34af..0ffcf702a11f81 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js @@ -5,7 +5,7 @@ import { ENTER, BACKSPACE, DELETE } from '@wordpress/keycodes'; import { useSelect, useDispatch } from '@wordpress/data'; import { useRefEffect } from '@wordpress/compose'; import { createRoot } from '@wordpress/element'; -import { store as blocksStore, getDefaultBlockName } from '@wordpress/blocks'; +import { store as blocksStore } from '@wordpress/blocks'; /** * Internal dependencies @@ -24,23 +24,14 @@ import BlockDraggableChip from '../../../components/block-draggable/draggable-ch */ export function useEventHandlers( { clientId, isSelected } ) { const { getBlockType } = useSelect( blocksStore ); - const { - getBlockRootClientId, - isZoomOut, - hasMultiSelection, - getBlockName, - canInsertBlockType, - getNextBlockClientId, - getBlockOrder, - getBlockEditingMode, - } = unlock( useSelect( blockEditorStore ) ); + const { getBlockRootClientId, isZoomOut, hasMultiSelection, getBlockName } = + unlock( useSelect( blockEditorStore ) ); const { insertAfterBlock, removeBlock, resetZoomLevel, startDraggingBlocks, stopDraggingBlocks, - selectBlock, } = unlock( useDispatch( blockEditorStore ) ); return useRefEffect( @@ -77,49 +68,13 @@ export function useEventHandlers( { clientId, isSelected } ) { return; } - event.preventDefault(); - - if ( keyCode === ENTER && isZoomOut() ) { - resetZoomLevel(); - } else if ( keyCode === ENTER ) { - const rootClientId = getBlockRootClientId( clientId ); - if ( - canInsertBlockType( - getDefaultBlockName(), - rootClientId - ) - ) { - insertAfterBlock( clientId ); - } else { - function getNextClientId( id ) { - let nextClientId = null; - - while ( - typeof id === 'string' && - ! ( nextClientId = getNextBlockClientId( id ) ) - ) { - id = getBlockRootClientId( id ); - } - - return nextClientId; - } - - let nextClientId = - getBlockOrder( clientId )[ 0 ] ?? - getNextClientId( clientId ); - - while ( - nextClientId && - getBlockEditingMode( nextClientId ) === 'disabled' - ) { - nextClientId = getNextClientId( nextClientId ); - } - - if ( nextClientId ) { - selectBlock( nextClientId, null ); - } + if ( keyCode === ENTER ) { + if ( isZoomOut() ) { + event.preventDefault(); + resetZoomLevel(); } } else { + event.preventDefault(); removeBlock( clientId ); } } diff --git a/packages/block-editor/src/components/writing-flow/use-input.js b/packages/block-editor/src/components/writing-flow/use-input.js index 0f10cc9c2d1c75..451f604286ae24 100644 --- a/packages/block-editor/src/components/writing-flow/use-input.js +++ b/packages/block-editor/src/components/writing-flow/use-input.js @@ -33,6 +33,9 @@ export default function useInput() { getSelectionStart, getSelectionEnd, getBlockAttributes, + getNextBlockClientId, + getBlockOrder, + getBlockEditingMode, } = useSelect( blockEditorStore ); const { replaceBlocks, @@ -41,6 +44,8 @@ export default function useInput() { __unstableDeleteSelection, __unstableExpandSelection, __unstableMarkAutomaticChange, + insertAfterBlock, + selectBlock, } = useDispatch( blockEditorStore ); return useRefEffect( ( node ) => { @@ -60,7 +65,7 @@ export default function useInput() { if ( ! hasMultiSelection() ) { if ( event.keyCode === ENTER ) { - if ( event.shiftKey || __unstableIsFullySelected() ) { + if ( event.shiftKey ) { return; } @@ -101,22 +106,62 @@ export default function useInput() { } } - if ( - ! hasBlockSupport( blockName, 'splitting', false ) && - ! event.__deprecatedOnSplit - ) { - return; - } + const rootClientId = getBlockRootClientId( clientId ); // Ensure template is not locked. if ( - canInsertBlockType( - blockName, - getBlockRootClientId( clientId ) - ) + canInsertBlockType( blockName, rootClientId ) && + ! hasBlockSupport( blockName, 'splitting', false ) && + ! event.__deprecatedOnSplit && + ! __unstableIsFullySelected() ) { - __unstableSplitSelection(); event.preventDefault(); + __unstableSplitSelection(); + } else if ( + event.target.ownerDocument.activeElement?.getAttribute( + 'data-block' + ) === clientId + ) { + if ( + canInsertBlockType( + getDefaultBlockName(), + rootClientId + ) + ) { + event.preventDefault(); + insertAfterBlock( clientId ); + } else { + function getNextClientId( id ) { + let nextClientId = null; + + while ( + typeof id === 'string' && + ! ( nextClientId = + getNextBlockClientId( id ) ) + ) { + id = getBlockRootClientId( id ); + } + + return nextClientId; + } + + let nextClientId = + getBlockOrder( clientId )[ 0 ] ?? + getNextClientId( clientId ); + + while ( + nextClientId && + getBlockEditingMode( nextClientId ) === + 'disabled' + ) { + nextClientId = getNextClientId( nextClientId ); + } + + if ( nextClientId ) { + event.preventDefault(); + selectBlock( nextClientId, null ); + } + } } } return; From 247d3d8a48d86333fb8159b22442a2afd57d899e Mon Sep 17 00:00:00 2001 From: Ella Date: Wed, 4 Dec 2024 16:31:54 +0100 Subject: [PATCH 04/11] fix condition --- .../block-editor/src/components/writing-flow/use-input.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/writing-flow/use-input.js b/packages/block-editor/src/components/writing-flow/use-input.js index 451f604286ae24..5f87ca335c8257 100644 --- a/packages/block-editor/src/components/writing-flow/use-input.js +++ b/packages/block-editor/src/components/writing-flow/use-input.js @@ -110,10 +110,10 @@ export default function useInput() { // Ensure template is not locked. if ( + ! __unstableIsFullySelected() && canInsertBlockType( blockName, rootClientId ) && - ! hasBlockSupport( blockName, 'splitting', false ) && - ! event.__deprecatedOnSplit && - ! __unstableIsFullySelected() + ( hasBlockSupport( blockName, 'splitting', false ) || + event.__deprecatedOnSplit ) ) { event.preventDefault(); __unstableSplitSelection(); From d5ea628d36adb075a4986a48cee3496bdb533972 Mon Sep 17 00:00:00 2001 From: Ella Date: Wed, 4 Dec 2024 17:33:59 +0100 Subject: [PATCH 05/11] Restore previous code --- .../use-block-props/use-selected-block-event-handlers.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js index 0ffcf702a11f81..03fd844440f711 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { isTextField } from '@wordpress/dom'; import { ENTER, BACKSPACE, DELETE } from '@wordpress/keycodes'; import { useSelect, useDispatch } from '@wordpress/data'; import { useRefEffect } from '@wordpress/compose'; @@ -52,10 +53,6 @@ export function useEventHandlers( { clientId, isSelected } ) { function onKeyDown( event ) { const { keyCode, target } = event; - if ( event.defaultPrevented ) { - return; - } - if ( keyCode !== ENTER && keyCode !== BACKSPACE && @@ -64,7 +61,7 @@ export function useEventHandlers( { clientId, isSelected } ) { return; } - if ( target !== node ) { + if ( target !== node || isTextField( target ) ) { return; } From e1f32ad6f047db6090e2f2927d79f3fa9493fe37 Mon Sep 17 00:00:00 2001 From: Ella Date: Wed, 4 Dec 2024 18:30:16 +0100 Subject: [PATCH 06/11] Adjust rich text enter behaviour --- .../src/components/block-list/block.js | 7 ++ .../rich-text/event-listeners/enter.js | 73 +++++++++++-------- .../src/components/rich-text/index.js | 3 + 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 29f8a97b031ce2..2e770be810a0a9 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -701,6 +701,11 @@ function BlockListBlockProvider( props ) { originalBlockClientId: isInvalid ? blocksWithSameName[ 0 ] : false, + supportsSplitting: hasBlockSupport( + blockName, + 'splitting', + false + ), }; }, [ clientId, rootClientId ] @@ -743,6 +748,7 @@ function BlockListBlockProvider( props ) { className, defaultClassName, originalBlockClientId, + supportsSplitting, } = selectedProps; // Users of the editor.BlockListBlock filter used to be able to @@ -791,6 +797,7 @@ function BlockListBlockProvider( props ) { originalBlockClientId, themeSupportsLayout, canMove, + supportsSplitting, }; // Here we separate between the props passed to BlockListBlock and any other diff --git a/packages/block-editor/src/components/rich-text/event-listeners/enter.js b/packages/block-editor/src/components/rich-text/event-listeners/enter.js index 63c2145fe7acbc..681a5fbff1a0dc 100644 --- a/packages/block-editor/src/components/rich-text/event-listeners/enter.js +++ b/packages/block-editor/src/components/rich-text/event-listeners/enter.js @@ -5,19 +5,51 @@ import { ENTER } from '@wordpress/keycodes'; import { insert, remove } from '@wordpress/rich-text'; export default ( props ) => ( element ) => { - function onKeyDownDeprecated( event ) { + function onKeyDown( event ) { if ( event.keyCode !== ENTER ) { return; } - const { onReplace, onSplit } = props.current; + const { + onReplace, + onSplit, + supportsSplitting, + disableLineBreaks, + onChange, + value, + onSplitAtDoubleLineEnd, + registry, + } = props.current; + const { text, start, end } = value; + + if ( ! supportsSplitting && ! disableLineBreaks ) { + event.preventDefault(); + if ( + // For some blocks it's desirable to split at the end of the + // block when there are two line breaks at the end of the + // block, so triple Enter exits the block. + onSplitAtDoubleLineEnd && + start === end && + end === text.length && + text.slice( -2 ) === '\n\n' + ) { + registry.batch( () => { + const _value = { ...value }; + _value.start = _value.end - 2; + onChange( remove( _value ) ); + onSplitAtDoubleLineEnd(); + } ); + } else { + onChange( insert( value, '\n' ) ); + } + } if ( onReplace && onSplit ) { event.__deprecatedOnSplit = true; } } - function onKeyDown( event ) { + function onDefaultKeyDown( event ) { if ( event.defaultPrevented ) { return; } @@ -32,14 +64,8 @@ export default ( props ) => ( element ) => { return; } - const { - value, - onChange, - disableLineBreaks, - onSplitAtEnd, - onSplitAtDoubleLineEnd, - registry, - } = props.current; + const { value, onChange, disableLineBreaks, onSplitAtEnd } = + props.current; event.preventDefault(); @@ -51,23 +77,6 @@ export default ( props ) => ( element ) => { } } else if ( onSplitAtEnd && start === end && end === text.length ) { onSplitAtEnd(); - } else if ( - // For some blocks it's desirable to split at the end of the - // block when there are two line breaks at the end of the - // block, so triple Enter exits the block. - onSplitAtDoubleLineEnd && - start === end && - end === text.length && - text.slice( -2 ) === '\n\n' - ) { - registry.batch( () => { - const _value = { ...value }; - _value.start = _value.end - 2; - onChange( remove( _value ) ); - onSplitAtDoubleLineEnd(); - } ); - } else if ( ! disableLineBreaks ) { - onChange( insert( value, '\n' ) ); } } @@ -75,10 +84,10 @@ export default ( props ) => ( element ) => { // Attach the listener to the window so parent elements have the chance to // prevent the default behavior. - defaultView.addEventListener( 'keydown', onKeyDown ); - element.addEventListener( 'keydown', onKeyDownDeprecated ); + defaultView.addEventListener( 'keydown', onDefaultKeyDown ); + element.addEventListener( 'keydown', onKeyDown ); return () => { - defaultView.removeEventListener( 'keydown', onKeyDown ); - element.removeEventListener( 'keydown', onKeyDownDeprecated ); + defaultView.removeEventListener( 'keydown', onDefaultKeyDown ); + element.removeEventListener( 'keydown', onKeyDown ); }; }; diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 768ffbb0cdd2dc..ea897e9c4be5bc 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -41,6 +41,7 @@ import { Content, valueToHTMLString } from './content'; import { withDeprecations } from './with-deprecations'; import { canBindBlock } from '../../utils/block-bindings'; import BlockContext from '../block-context'; +import { PrivateBlockContext } from '../block-list/private-block-context'; export const keyboardShortcutContext = createContext(); export const inputEventContext = createContext(); @@ -118,6 +119,7 @@ export function RichTextWrapper( } ); } + const { supportsSplitting } = useContext( PrivateBlockContext ); const instanceId = useInstanceId( RichTextWrapper ); const anchorRef = useRef(); const context = useBlockEditContext(); @@ -471,6 +473,7 @@ export function RichTextWrapper( onSplitAtDoubleLineEnd, keyboardShortcuts, inputEvents, + supportsSplitting, } ), anchorRef, ] ) } From a3b2fdb98a2b4bdee33713a87c7bc34e278edd02 Mon Sep 17 00:00:00 2001 From: Ella Date: Wed, 4 Dec 2024 19:40:59 +0100 Subject: [PATCH 07/11] Adjust multi line --- .../rich-text/event-listeners/enter.js | 6 +- .../src/components/rich-text/index.js | 8 +- .../src/components/rich-text/multiline.js | 202 ++++++++++-------- 3 files changed, 126 insertions(+), 90 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/event-listeners/enter.js b/packages/block-editor/src/components/rich-text/event-listeners/enter.js index 681a5fbff1a0dc..a36426954eef54 100644 --- a/packages/block-editor/src/components/rich-text/event-listeners/enter.js +++ b/packages/block-editor/src/components/rich-text/event-listeners/enter.js @@ -22,7 +22,11 @@ export default ( props ) => ( element ) => { } = props.current; const { text, start, end } = value; - if ( ! supportsSplitting && ! disableLineBreaks ) { + if ( + ! supportsSplitting && + ! disableLineBreaks && + ! event.defaultPrevented + ) { event.preventDefault(); if ( // For some blocks it's desirable to split at the end of the diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index ea897e9c4be5bc..9c67bae1a4c5fd 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -78,7 +78,7 @@ function removeNativeProps( props ) { return restProps; } -export function RichTextWrapper( +function _RichTextWrapper( { children, tagName = 'div', @@ -501,11 +501,11 @@ export function RichTextWrapper( ); } +export const RichTextWrapper = forwardRef( _RichTextWrapper ); + // This is the private API for the RichText component. // It allows access to all props, not just the public ones. -export const PrivateRichText = withDeprecations( - forwardRef( RichTextWrapper ) -); +export const PrivateRichText = withDeprecations( RichTextWrapper ); PrivateRichText.Content = Content; PrivateRichText.isEmpty = ( value ) => { diff --git a/packages/block-editor/src/components/rich-text/multiline.js b/packages/block-editor/src/components/rich-text/multiline.js index 1fa77b25a4db9c..73fa7b0904ba0b 100644 --- a/packages/block-editor/src/components/rich-text/multiline.js +++ b/packages/block-editor/src/components/rich-text/multiline.js @@ -1,11 +1,12 @@ /** * WordPress dependencies */ -import { forwardRef } from '@wordpress/element'; +import { forwardRef, useInsertionEffect, useRef } from '@wordpress/element'; import deprecated from '@wordpress/deprecated'; import { useDispatch, useSelect } from '@wordpress/data'; import { ENTER } from '@wordpress/keycodes'; import { create, split, toHTMLString } from '@wordpress/rich-text'; +import { useRefEffect } from '@wordpress/compose'; /** * Internal dependencies @@ -15,6 +16,115 @@ import { store as blockEditorStore } from '../../store'; import { useBlockEditContext } from '../block-edit'; import { getMultilineTag } from './utils'; +function useEnterRef( props ) { + const { getSelectionStart, getSelectionEnd } = + useSelect( blockEditorStore ); + const { selectionChange } = useDispatch( blockEditorStore ); + return useRefEffect( ( element ) => { + function onKeyDown( event ) { + if ( event.keyCode !== ENTER ) { + return; + } + + event.preventDefault(); + + const { value, values, onChange, index, identifier, clientId } = + props.current; + const { offset: start } = getSelectionStart(); + const { offset: end } = getSelectionEnd(); + + // Cannot split if there is no selection. + if ( typeof start !== 'number' || typeof end !== 'number' ) { + return; + } + + const richTextValue = create( { html: value } ); + richTextValue.start = start; + richTextValue.end = end; + + const array = split( richTextValue ).map( ( v ) => + toHTMLString( { value: v } ) + ); + + const newValues = values.slice(); + newValues.splice( index, 1, ...array ); + onChange( newValues ); + selectionChange( clientId, `${ identifier }-${ index + 1 }`, 0, 0 ); + } + element.addEventListener( 'keydown', onKeyDown ); + return () => { + element.removeEventListener( 'keydown', onKeyDown ); + }; + }, [] ); +} + +function Line( lineProps ) { + const { + value, + values, + onChange, + index, + identifier, + multilineTagName, + ...props + } = lineProps; + const { clientId } = useBlockEditContext(); + const { selectionChange } = useDispatch( blockEditorStore ); + const latestRef = useRef(); + useInsertionEffect( () => { + latestRef.current = { ...lineProps, clientId }; + } ); + const ref = useEnterRef( latestRef ); + return ( + { + const newValues = values.slice(); + newValues[ index ] = newValue; + onChange( newValues ); + } } + isSelected={ undefined } + ref={ ref } + onMerge={ ( forward ) => { + const newValues = values.slice(); + let offset = 0; + if ( forward ) { + if ( ! newValues[ index + 1 ] ) { + return; + } + newValues.splice( + index, + 2, + newValues[ index ] + newValues[ index + 1 ] + ); + offset = newValues[ index ].length - 1; + } else { + if ( ! newValues[ index - 1 ] ) { + return; + } + newValues.splice( + index - 1, + 2, + newValues[ index - 1 ] + newValues[ index ] + ); + offset = newValues[ index - 1 ].length - 1; + } + onChange( newValues ); + selectionChange( + clientId, + `${ identifier }-${ index - ( forward ? 0 : 1 ) }`, + offset, + offset + ); + } } + { ...props } + /> + ); +} + function RichTextMultiline( { children, @@ -34,11 +144,6 @@ function RichTextMultiline( link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/nested-blocks-inner-blocks/', } ); - const { clientId } = useBlockEditContext(); - const { getSelectionStart, getSelectionEnd } = - useSelect( blockEditorStore ); - const { selectionChange } = useDispatch( blockEditorStore ); - const multilineTagName = getMultilineTag( multiline ); value = value || `<${ multilineTagName }>`; const padded = `${ value }<${ multilineTagName }>`; @@ -61,87 +166,14 @@ function RichTextMultiline( { values.map( ( _value, index ) => { return ( - { - const newValues = values.slice(); - newValues[ index ] = newValue; - _onChange( newValues ); - } } - isSelected={ undefined } - onKeyDown={ ( event ) => { - if ( event.keyCode !== ENTER ) { - return; - } - - event.preventDefault(); - - const { offset: start } = getSelectionStart(); - const { offset: end } = getSelectionEnd(); - - // Cannot split if there is no selection. - if ( - typeof start !== 'number' || - typeof end !== 'number' - ) { - return; - } - - const richTextValue = create( { html: _value } ); - richTextValue.start = start; - richTextValue.end = end; - - const array = split( richTextValue ).map( ( v ) => - toHTMLString( { value: v } ) - ); - - const newValues = values.slice(); - newValues.splice( index, 1, ...array ); - _onChange( newValues ); - selectionChange( - clientId, - `${ identifier }-${ index + 1 }`, - 0, - 0 - ); - } } - onMerge={ ( forward ) => { - const newValues = values.slice(); - let offset = 0; - if ( forward ) { - if ( ! newValues[ index + 1 ] ) { - return; - } - newValues.splice( - index, - 2, - newValues[ index ] + newValues[ index + 1 ] - ); - offset = newValues[ index ].length - 1; - } else { - if ( ! newValues[ index - 1 ] ) { - return; - } - newValues.splice( - index - 1, - 2, - newValues[ index - 1 ] + newValues[ index ] - ); - offset = newValues[ index - 1 ].length - 1; - } - _onChange( newValues ); - selectionChange( - clientId, - `${ identifier }-${ - index - ( forward ? 0 : 1 ) - }`, - offset, - offset - ); - } } + onChange={ _onChange } + index={ index } + identifier={ identifier } + multilineTagName={ multilineTagName } { ...props } /> ); From df3423b246f984fbc1843bb7bfb9ba74b166d7fa Mon Sep 17 00:00:00 2001 From: Ella Date: Thu, 5 Dec 2024 14:53:41 +0100 Subject: [PATCH 08/11] move shift --- .../rich-text/event-listeners/enter.js | 32 ++++++++----------- .../editor/plugins/post-type-locking.spec.js | 2 +- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/event-listeners/enter.js b/packages/block-editor/src/components/rich-text/event-listeners/enter.js index a36426954eef54..f2308deddd676a 100644 --- a/packages/block-editor/src/components/rich-text/event-listeners/enter.js +++ b/packages/block-editor/src/components/rich-text/event-listeners/enter.js @@ -19,10 +19,21 @@ export default ( props ) => ( element ) => { value, onSplitAtDoubleLineEnd, registry, + onSplitAtEnd, } = props.current; const { text, start, end } = value; - if ( + if ( event.shiftKey ) { + if ( ! disableLineBreaks ) { + event.preventDefault(); + onChange( insert( value, '\n' ) ); + } + } else if ( onSplitAtEnd && start === end && end === text.length ) { + event.preventDefault(); + onSplitAtEnd(); + } else if ( onReplace && onSplit ) { + event.__deprecatedOnSplit = true; + } else if ( ! supportsSplitting && ! disableLineBreaks && ! event.defaultPrevented @@ -47,10 +58,6 @@ export default ( props ) => ( element ) => { onChange( insert( value, '\n' ) ); } } - - if ( onReplace && onSplit ) { - event.__deprecatedOnSplit = true; - } } function onDefaultKeyDown( event ) { @@ -68,20 +75,9 @@ export default ( props ) => ( element ) => { return; } - const { value, onChange, disableLineBreaks, onSplitAtEnd } = - props.current; - + // On ENTER, we ALWAYS want to prevent the default browser behaviour + // at this last interception point. event.preventDefault(); - - const { text, start, end } = value; - - if ( event.shiftKey ) { - if ( ! disableLineBreaks ) { - onChange( insert( value, '\n' ) ); - } - } else if ( onSplitAtEnd && start === end && end === text.length ) { - onSplitAtEnd(); - } } const { defaultView } = element.ownerDocument; diff --git a/test/e2e/specs/editor/plugins/post-type-locking.spec.js b/test/e2e/specs/editor/plugins/post-type-locking.spec.js index ff02d18c51464b..bfdacabdd44f46 100644 --- a/test/e2e/specs/editor/plugins/post-type-locking.spec.js +++ b/test/e2e/specs/editor/plugins/post-type-locking.spec.js @@ -91,7 +91,7 @@ test.describe( 'Post-type locking', () => { .click(); await page.keyboard.type( 'First line' ); - await page.keyboard.press( 'Enter' ); + await pageUtils.pressKeys( 'shift+Enter' ); await page.keyboard.type( 'Second line' ); await pageUtils.pressKeys( 'shift+Enter' ); await page.keyboard.type( 'Third line' ); From 211052cd6ff504e0fc010909e7580c361c136328 Mon Sep 17 00:00:00 2001 From: Ella Date: Thu, 5 Dec 2024 15:06:04 +0100 Subject: [PATCH 09/11] Preserve original selectBlock behaviour --- .../block-list/use-block-props/use-focus-first-element.js | 6 +++++- .../block-editor/src/components/writing-flow/use-input.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js b/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js index 40262c99e19ddf..d5131e12f02977 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js @@ -43,6 +43,10 @@ export function useFocusFirstElement( { clientId, initialPosition } ) { return; } + if ( initialPosition === undefined || initialPosition === null ) { + return; + } + if ( ! ref.current ) { return; } @@ -54,7 +58,7 @@ export function useFocusFirstElement( { clientId, initialPosition } ) { return; } - if ( initialPosition === undefined || initialPosition === null ) { + if ( initialPosition === true ) { ref.current.focus(); return; } diff --git a/packages/block-editor/src/components/writing-flow/use-input.js b/packages/block-editor/src/components/writing-flow/use-input.js index 5f87ca335c8257..204c2c4913df81 100644 --- a/packages/block-editor/src/components/writing-flow/use-input.js +++ b/packages/block-editor/src/components/writing-flow/use-input.js @@ -159,7 +159,7 @@ export default function useInput() { if ( nextClientId ) { event.preventDefault(); - selectBlock( nextClientId, null ); + selectBlock( nextClientId, true ); } } } From 1aca4b55166bd722d47aa2396feb2d29754a5046 Mon Sep 17 00:00:00 2001 From: Ella Date: Thu, 5 Dec 2024 15:06:28 +0100 Subject: [PATCH 10/11] Remove onSplitAtEnd --- .../src/components/rich-text/event-listeners/enter.js | 1 + packages/block-library/src/post-title/edit.js | 9 ++------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/event-listeners/enter.js b/packages/block-editor/src/components/rich-text/event-listeners/enter.js index f2308deddd676a..75a265f8da9b2e 100644 --- a/packages/block-editor/src/components/rich-text/event-listeners/enter.js +++ b/packages/block-editor/src/components/rich-text/event-listeners/enter.js @@ -29,6 +29,7 @@ export default ( props ) => ( element ) => { onChange( insert( value, '\n' ) ); } } else if ( onSplitAtEnd && start === end && end === text.length ) { + // This is no longer used anywhere. Deprecate? event.preventDefault(); onSplitAtEnd(); } else if ( onReplace && onSplit ) { diff --git a/packages/block-library/src/post-title/edit.js b/packages/block-library/src/post-title/edit.js index fdfb8e7c2c6cd2..a7137be35d4420 100644 --- a/packages/block-library/src/post-title/edit.js +++ b/packages/block-library/src/post-title/edit.js @@ -17,7 +17,6 @@ import { } from '@wordpress/block-editor'; import { ToggleControl, TextControl, PanelBody } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { createBlock, getDefaultBlockName } from '@wordpress/blocks'; import { useEntityProp, store as coreStore } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; @@ -25,7 +24,6 @@ export default function PostTitleEdit( { attributes: { level, levelOptions, textAlign, isLink, rel, linkTarget }, setAttributes, context: { postType, postId, queryId }, - insertBlocksAfter, } ) { const TagName = level === 0 ? 'p' : `h${ level }`; const isDescendentOfQueryLoop = Number.isFinite( queryId ); @@ -57,9 +55,6 @@ export default function PostTitleEdit( { postId ); const [ link ] = useEntityProp( 'postType', postType, 'link', postId ); - const onSplitAtEnd = () => { - insertBlocksAfter( createBlock( getDefaultBlockName() ) ); - }; const blockProps = useBlockProps( { className: clsx( { [ `has-text-align-${ textAlign }` ]: textAlign, @@ -77,7 +72,7 @@ export default function PostTitleEdit( { value={ rawTitle } onChange={ setTitle } __experimentalVersion={ 2 } - __unstableOnSplitAtEnd={ onSplitAtEnd } + disableLineBreaks { ...blockProps } /> ) : ( @@ -100,7 +95,7 @@ export default function PostTitleEdit( { value={ rawTitle } onChange={ setTitle } __experimentalVersion={ 2 } - __unstableOnSplitAtEnd={ onSplitAtEnd } + disableLineBreaks /> ) : ( From a603ab9e26576799ba6a38553cd893af7dd80fa8 Mon Sep 17 00:00:00 2001 From: Ella Date: Wed, 18 Dec 2024 14:32:59 +0900 Subject: [PATCH 11/11] Add e2e test --- .../specs/site-editor/writing-flow.spec.js | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/e2e/specs/site-editor/writing-flow.spec.js b/test/e2e/specs/site-editor/writing-flow.spec.js index ad661175b6bdfe..6affa7f9240b48 100644 --- a/test/e2e/specs/site-editor/writing-flow.spec.js +++ b/test/e2e/specs/site-editor/writing-flow.spec.js @@ -70,4 +70,42 @@ test.describe( 'Site editor writing flow', () => { ); await expect( inspectorBlockTab ).toBeFocused(); } ); + + test( 'enter selects the next block', async ( { + admin, + editor, + page, + requestUtils, + } ) => { + const { id } = await requestUtils.createPage( { + status: 'draft', + title: 'test', + } ); + + await admin.visitSiteEditor( { + postId: id, + postType: 'page', + canvas: 'edit', + } ); + + // select the first block + const firstBlock = editor.canvas.locator( + 'role=document[name="Block: Title"i]' + ); + await editor.selectBlocks( firstBlock ); + + await expect( firstBlock ).toBeFocused(); + + await page.keyboard.press( 'Enter' ); + const secondBlock = editor.canvas.locator( + 'role=document[name="Block: Content"i]' + ); + await expect( secondBlock ).toBeFocused(); + + await page.keyboard.press( 'Enter' ); + const thirdBlock = editor.canvas.getByRole( 'document', { + name: 'Empty block', + } ); + await expect( thirdBlock ).toBeFocused(); + } ); } );