From db8da1521b2a602753231bb6b90420c2ae552376 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 11 Feb 2021 15:31:05 +0100 Subject: [PATCH] Avoid focusing blocks when inserting them into the canvas (#28191) --- .../developers/data/data-core-block-editor.md | 10 ++- .../developers/data/data-core-editor.md | 16 +++++ .../src/components/block-list/index.js | 2 + .../use-focus-first-element.js | 6 +- .../components/block-navigation/appender.js | 1 - .../components/button-block-appender/index.js | 9 +-- packages/block-editor/src/components/index.js | 5 +- .../use-inner-block-template-sync.js | 13 +++- .../components/inserter-list-item/index.js | 33 +++++++++- .../inserter/hooks/use-block-types-state.js | 4 +- .../inserter/hooks/use-insertion-point.js | 44 +++++++------ .../src/components/inserter/index.js | 29 +++------ .../src/components/inserter/library.js | 5 +- .../src/components/inserter/menu.js | 12 ++-- .../src/components/inserter/quick-inserter.js | 3 - .../src/components/inserter/search-results.js | 4 +- .../provider/test/use-block-sync.js | 47 +++++++++++--- .../src/components/provider/use-block-sync.js | 23 +++---- .../src/components/rich-text/index.js | 2 +- .../index.js | 29 +++------ .../src/components/use-on-block-drop/index.js | 8 ++- packages/block-editor/src/store/actions.js | 61 ++++++++++++------- packages/block-editor/src/store/reducer.js | 30 +++++---- packages/block-editor/src/store/selectors.js | 3 +- .../block-editor/src/store/test/actions.js | 10 +++ .../block-editor/src/store/test/reducer.js | 33 ---------- packages/core-data/src/entities.js | 3 +- packages/core-data/src/entity-provider.js | 8 +-- packages/e2e-test-utils/src/inserter.js | 27 ++++++++ .../inner-blocks-allowed-blocks.test.js | 5 ++ .../specs/editor/various/writing-flow.test.js | 5 ++ .../specs/widgets/adding-widgets.test.js | 10 ++- .../src/components/visual-editor/index.js | 2 - .../editor/src/components/provider/index.js | 16 ++--- .../src/components/provider/index.native.js | 3 +- packages/editor/src/store/actions.js | 8 +-- packages/editor/src/store/selectors.js | 24 +++++++- 37 files changed, 325 insertions(+), 228 deletions(-) rename packages/block-editor/src/components/{multi-select-scroll-into-view => selection-scroll-into-view}/index.js (69%) diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index ded41c1a63dfc..05bd3906353ff 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -657,6 +657,7 @@ _Returns_ Returns the initial caret position for the selected block. This position is to used to position the caret properly when the selected block changes. +If the current block is not a RichText, having initial position set to 0 means "focus block" _Parameters_ @@ -664,7 +665,7 @@ _Parameters_ _Returns_ -- `?Object`: Selected block. +- `(||null)`: Initial position. # **getSelectionEnd** @@ -1138,6 +1139,7 @@ _Parameters_ - _index_ `?number`: Index at which block should be inserted. - _rootClientId_ `?string`: Optional root client ID of block list on which to insert. - _updateSelection_ `?boolean`: If true block selection will be updated. If false, block selection will not change. Defaults to true. +- _initialPosition_ `(||null)`: Initial focus position. Setting it to null prevent focusing the inserted block. - _meta_ `?Object`: Optional Meta values to be passed to the action object. _Returns_ @@ -1271,7 +1273,7 @@ _Parameters_ - _clientIds_ `(string|Array)`: Block client ID(s) to replace. - _blocks_ `(Object|Array)`: Replacement block(s). - _indexToSelect_ `number`: Index of replacement block to select. -- _initialPosition_ `number`: Index of caret after in the selected block after the operation. +- _initialPosition_ `(||null)`: Index of caret after in the selected block after the operation. - _meta_ `?Object`: Optional Meta values to be passed to the action object. # **replaceInnerBlocks** @@ -1284,6 +1286,7 @@ _Parameters_ - _rootClientId_ `string`: Client ID of the block whose InnerBlocks will re replaced. - _blocks_ `Array`: Block objects to insert as new InnerBlocks - _updateSelection_ `?boolean`: If true block selection will be updated. If false, block selection will not change. Defaults to false. +- _initialPosition_ `(||null)`: Initial block position. _Returns_ @@ -1308,6 +1311,7 @@ _Parameters_ - _selectionStart_ `WPBlockSelection`: The selection start. - _selectionEnd_ `WPBlockSelection`: The selection end. +- _initialPosition_ `(||null)`: Initial block position. _Returns_ @@ -1323,7 +1327,7 @@ reflects a reverse selection. _Parameters_ - _clientId_ `string`: Block client ID. -- _initialPosition_ `?number`: Optional initial position. Pass as -1 to reflect reverse selection. +- _initialPosition_ `(||null)`: Optional initial position. Pass as -1 to reflect reverse selection. _Returns_ diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/designers-developers/developers/data/data-core-editor.md index 621461421e085..86e57d27a53e3 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/designers-developers/developers/data/data-core-editor.md @@ -373,8 +373,22 @@ _Returns_ - `Array`: Block list. +# **getEditorSelection** + +Returns the current selection. + +_Parameters_ + +- _state_ `Object`: + +_Returns_ + +- `WPBlockSelection`: The selection end. + # **getEditorSelectionEnd** +> **Deprecated** since Gutenberg 10.0.0. + Returns the current selection end. _Parameters_ @@ -387,6 +401,8 @@ _Returns_ # **getEditorSelectionStart** +> **Deprecated** since Gutenberg 10.0.0. + Returns the current selection start. _Parameters_ diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 0192290a585e9..292243ea02c54 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -18,6 +18,7 @@ import useBlockDropZone from '../use-block-drop-zone'; import useInsertionPoint from './insertion-point'; import BlockPopover from './block-popover'; import { store as blockEditorStore } from '../../store'; +import { useScrollSelectionIntoView } from '../selection-scroll-into-view'; /** * If the block count exceeds the threshold, we disable the reordering animation @@ -32,6 +33,7 @@ export default function BlockList( { className } ) { const ref = useRef(); const [ blockNodes, setBlockNodes ] = useState( {} ); const insertionPoint = useInsertionPoint( ref ); + useScrollSelectionIntoView( ref ); return ( 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 eee3b88be62d0..da048d18acd90 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 @@ -45,7 +45,7 @@ function useInitialPosition( clientId ) { } // If there's no initial position, return 0 to focus the start. - return getSelectedBlocksInitialCaretPosition() || 0; + return getSelectedBlocksInitialCaretPosition(); }, [ clientId ] ); @@ -53,7 +53,7 @@ function useInitialPosition( clientId ) { /** * Transitions focus to the block or inner tabbable when the block becomes - * selected. + * selected and an initial position is set. * * @param {RefObject} ref React ref with the block element. * @param {string} clientId Block client ID. @@ -62,7 +62,7 @@ export function useFocusFirstElement( ref, clientId ) { const initialPosition = useInitialPosition( clientId ); useEffect( () => { - if ( initialPosition === undefined ) { + if ( initialPosition === undefined || initialPosition === null ) { return; } diff --git a/packages/block-editor/src/components/block-navigation/appender.js b/packages/block-editor/src/components/block-navigation/appender.js index bcb414ec46feb..391b72daccd23 100644 --- a/packages/block-editor/src/components/block-navigation/appender.js +++ b/packages/block-editor/src/components/block-navigation/appender.js @@ -67,7 +67,6 @@ export default function BlockNavigationAppender( { diff --git a/packages/block-editor/src/components/button-block-appender/index.js b/packages/block-editor/src/components/button-block-appender/index.js index a37acc0429df7..eecd1bf51af17 100644 --- a/packages/block-editor/src/components/button-block-appender/index.js +++ b/packages/block-editor/src/components/button-block-appender/index.js @@ -17,20 +17,13 @@ import { Icon, plus } from '@wordpress/icons'; import Inserter from '../inserter'; function ButtonBlockAppender( - { - rootClientId, - className, - __experimentalSelectBlockOnInsert: selectBlockOnInsert, - onFocus, - tabIndex, - }, + { rootClientId, className, onFocus, tabIndex }, ref ) { return ( + select( blockEditorStore ).getSelectedBlocksInitialCaretPosition, + [] + ); const { replaceInnerBlocks } = useDispatch( blockEditorStore ); - const innerBlocks = useSelect( ( select ) => select( blockEditorStore ).getBlocks( clientId ), [ clientId ] @@ -69,7 +73,12 @@ export default function useInnerBlockTemplateSync( nextBlocks, innerBlocks.length === 0 && templateInsertUpdatesSelection && - nextBlocks.length !== 0 + nextBlocks.length !== 0, + // This ensures the "initialPosition" doesn't change when applying the template + // If we're supposed to focus the block, we'll focus the first inner block + // otherwise, we won't apply any auto-focus. + // This ensures for instance that the focus stays in the inserter when inserting the "buttons" block. + getSelectedBlocksInitialCaretPosition() ); } } diff --git a/packages/block-editor/src/components/inserter-list-item/index.js b/packages/block-editor/src/components/inserter-list-item/index.js index 47e48ce8ddf29..e2fb004fedbe2 100644 --- a/packages/block-editor/src/components/inserter-list-item/index.js +++ b/packages/block-editor/src/components/inserter-list-item/index.js @@ -15,6 +15,7 @@ import { createBlock, createBlocksFromInnerBlocksTemplate, } from '@wordpress/blocks'; +import { ENTER } from '@wordpress/keycodes'; /** * Internal dependencies @@ -22,6 +23,22 @@ import { import BlockIcon from '../block-icon'; import InserterDraggableBlocks from '../inserter-draggable-blocks'; +/** + * Return true if platform is MacOS. + * + * @param {Object} _window window object by default; used for DI testing. + * + * @return {boolean} True if MacOS; false otherwise. + */ +function isAppleOS( _window = window ) { + const { platform } = _window.navigator; + + return ( + platform.indexOf( 'Mac' ) !== -1 || + [ 'iPad', 'iPhone' ].includes( platform ) + ); +} + function InserterListItem( { className, composite, @@ -83,9 +100,23 @@ function InserterListItem( { disabled={ item.isDisabled } onClick={ ( event ) => { event.preventDefault(); - onSelect( item ); + onSelect( + item, + isAppleOS() ? event.metaKey : event.ctrlKey + ); onHover( null ); } } + onKeyDown={ ( event ) => { + const { keyCode } = event; + if ( keyCode === ENTER ) { + event.preventDefault(); + onSelect( + item, + isAppleOS() ? event.metaKey : event.ctrlKey + ); + onHover( null ); + } + } } onFocus={ () => { if ( isDragging.current ) { return; diff --git a/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js b/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js index d54a4f019e151..1184fe72f75a5 100644 --- a/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js +++ b/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js @@ -37,14 +37,14 @@ const useBlockTypesState = ( rootClientId, onInsert ) => { ); const onSelectItem = useCallback( - ( { name, initialAttributes, innerBlocks } ) => { + ( { name, initialAttributes, innerBlocks }, shouldFocusBlock ) => { const insertedBlock = createBlock( name, initialAttributes, createBlocksFromInnerBlocksTemplate( innerBlocks ) ); - onInsert( insertedBlock ); + onInsert( insertedBlock, undefined, shouldFocusBlock ); }, [ onInsert ] ); diff --git a/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js b/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js index 0fffe4ec0a90d..069b33f6bc0ac 100644 --- a/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js +++ b/packages/block-editor/src/components/inserter/hooks/use-insertion-point.js @@ -28,8 +28,6 @@ import { store as blockEditorStore } from '../../../store'; * block with this ID. * @property {boolean=} isAppender Whether the inserter is an appender * or not. - * @property {boolean=} selectBlockOnInsert Whether the block should be - * selected on insert. * @property {Function=} onSelect Called after insertion. */ @@ -44,17 +42,17 @@ function useInsertionPoint( { insertionIndex, clientId, isAppender, - selectBlockOnInsert, onSelect, + shouldFocusBlock = true, } ) { const { - selectedBlock, destinationRootClientId, destinationIndex, + getSelectedBlock, } = useSelect( ( select ) => { const { - getSelectedBlock, + getSelectedBlock: _getSelectedBlock, getBlockIndex, getBlockOrder, getBlockInsertionPoint, @@ -92,7 +90,7 @@ function useInsertionPoint( { } return { - selectedBlock: getSelectedBlock(), + getSelectedBlock: _getSelectedBlock, destinationRootClientId: _destinationRootClientId, destinationIndex: _destinationIndex, }; @@ -108,7 +106,9 @@ function useInsertionPoint( { } = useDispatch( blockEditorStore ); const onInsertBlocks = useCallback( - ( blocks, meta ) => { + ( blocks, meta, shouldForceFocusBlock = false ) => { + const selectedBlock = getSelectedBlock(); + if ( ! isAppender && selectedBlock && @@ -118,7 +118,7 @@ function useInsertionPoint( { selectedBlock.clientId, blocks, null, - null, + shouldFocusBlock || shouldForceFocusBlock ? 0 : null, meta ); } else { @@ -126,23 +126,21 @@ function useInsertionPoint( { blocks, destinationIndex, destinationRootClientId, - selectBlockOnInsert, + true, + shouldFocusBlock || shouldForceFocusBlock ? 0 : null, meta ); } - - if ( ! selectBlockOnInsert ) { - const message = sprintf( - // translators: %d: the name of the block that has been added - _n( - '%d block added.', - '%d blocks added.', - castArray( blocks ).length - ), + const message = sprintf( + // translators: %d: the name of the block that has been added + _n( + '%d block added.', + '%d blocks added.', castArray( blocks ).length - ); - speak( message ); - } + ), + castArray( blocks ).length + ); + speak( message ); if ( onSelect ) { onSelect(); @@ -150,13 +148,13 @@ function useInsertionPoint( { }, [ isAppender, - selectedBlock, + getSelectedBlock, replaceBlocks, insertBlocks, destinationRootClientId, destinationIndex, - selectBlockOnInsert, onSelect, + shouldFocusBlock, ] ); diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index 02032bc6e214e..0e197be4207bd 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -132,7 +132,6 @@ class Inserter extends Component { clientId, isAppender, showInserterHelpPanel, - __experimentalSelectBlockOnInsert: selectBlockOnInsert, // This prop is experimental to give some time for the quick inserter to mature // Feel free to make them stable after a few releases. @@ -148,7 +147,6 @@ class Inserter extends Component { rootClientId={ rootClientId } clientId={ clientId } isAppender={ isAppender } - selectBlockOnInsert={ selectBlockOnInsert } /> ); } @@ -162,7 +160,6 @@ class Inserter extends Component { clientId={ clientId } isAppender={ isAppender } showInserterHelpPanel={ showInserterHelpPanel } - __experimentalSelectBlockOnInsert={ selectBlockOnInsert } /> ); } @@ -239,12 +236,9 @@ export default compose( [ rootClientId, clientId, isAppender, - onSelectOrClose, - } = ownProps; - const { hasSingleBlockType, allowedBlockType, - __experimentalSelectBlockOnInsert: selectBlockOnInsert, + onSelectOrClose, } = ownProps; if ( ! hasSingleBlockType ) { @@ -277,25 +271,18 @@ export default compose( [ const blockToInsert = createBlock( allowedBlockType.name ); - insertBlock( - blockToInsert, - getInsertionIndex(), - rootClientId, - selectBlockOnInsert - ); + insertBlock( blockToInsert, getInsertionIndex(), rootClientId ); if ( onSelectOrClose ) { onSelectOrClose(); } - if ( ! selectBlockOnInsert ) { - const message = sprintf( - // translators: %s: the name of the block that has been added - __( '%s block added' ), - allowedBlockType.title - ); - speak( message ); - } + const message = sprintf( + // translators: %s: the name of the block that has been added + __( '%s block added' ), + allowedBlockType.title + ); + speak( message ); }, }; } ), diff --git a/packages/block-editor/src/components/inserter/library.js b/packages/block-editor/src/components/inserter/library.js index ab9dbbb900eff..fc69a8f6d82e7 100644 --- a/packages/block-editor/src/components/inserter/library.js +++ b/packages/block-editor/src/components/inserter/library.js @@ -20,7 +20,6 @@ function InserterLibrary( { isAppender, showInserterHelpPanel, showMostUsedBlocks = false, - __experimentalSelectBlockOnInsert, __experimentalInsertionIndex, onSelect = noop, } ) { @@ -43,10 +42,8 @@ function InserterLibrary( { isAppender={ isAppender } showInserterHelpPanel={ showInserterHelpPanel } showMostUsedBlocks={ showMostUsedBlocks } - __experimentalSelectBlockOnInsert={ - __experimentalSelectBlockOnInsert - } __experimentalInsertionIndex={ __experimentalInsertionIndex } + shouldFocusBlock={ false } /> ); } diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 9cd0346d30ba9..52d3834cb8384 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -24,11 +24,11 @@ function InserterMenu( { rootClientId, clientId, isAppender, - __experimentalSelectBlockOnInsert, __experimentalInsertionIndex, onSelect, showInserterHelpPanel, showMostUsedBlocks, + shouldFocusBlock = true, } ) { const [ filterValue, setFilterValue ] = useState( '' ); const [ hoveredItem, setHoveredItem ] = useState( null ); @@ -44,8 +44,8 @@ function InserterMenu( { rootClientId, clientId, isAppender, - selectBlockOnInsert: __experimentalSelectBlockOnInsert, insertionIndex: __experimentalInsertionIndex, + shouldFocusBlock, } ); const { showPatterns, hasReusableBlocks } = useSelect( ( select ) => { @@ -67,8 +67,8 @@ function InserterMenu( { ); const onInsert = useCallback( - ( blocks ) => { - onInsertBlocks( blocks ); + ( blocks, meta, shouldForceFocusBlock ) => { + onInsertBlocks( blocks, meta, shouldForceFocusBlock ); onSelect(); }, [ onInsertBlocks, onSelect ] @@ -189,10 +189,8 @@ function InserterMenu( { rootClientId={ rootClientId } clientId={ clientId } isAppender={ isAppender } - selectBlockOnInsert={ - __experimentalSelectBlockOnInsert - } showBlockDirectory + shouldFocusBlock={ shouldFocusBlock } /> ) } { ! filterValue && ( showPatterns || hasReusableBlocks ) && ( diff --git a/packages/block-editor/src/components/inserter/quick-inserter.js b/packages/block-editor/src/components/inserter/quick-inserter.js index ad3a804fc54dc..3733cc12eecac 100644 --- a/packages/block-editor/src/components/inserter/quick-inserter.js +++ b/packages/block-editor/src/components/inserter/quick-inserter.js @@ -30,7 +30,6 @@ export default function QuickInserter( { rootClientId, clientId, isAppender, - selectBlockOnInsert, } ) { const [ filterValue, setFilterValue ] = useState( '' ); const [ destinationRootClientId, onInsertBlocks ] = useInsertionPoint( { @@ -38,7 +37,6 @@ export default function QuickInserter( { rootClientId, clientId, isAppender, - selectBlockOnInsert, } ); const [ blockTypes ] = useBlockTypesState( destinationRootClientId, @@ -105,7 +103,6 @@ export default function QuickInserter( { rootClientId={ rootClientId } clientId={ clientId } isAppender={ isAppender } - selectBlockOnInsert={ selectBlockOnInsert } maxBlockPatterns={ showPatterns ? SHOWN_BLOCK_PATTERNS : 0 } maxBlockTypes={ SHOWN_BLOCK_TYPES } isDraggable={ false } diff --git a/packages/block-editor/src/components/inserter/search-results.js b/packages/block-editor/src/components/inserter/search-results.js index 00d34c814170b..0dae29a230556 100644 --- a/packages/block-editor/src/components/inserter/search-results.js +++ b/packages/block-editor/src/components/inserter/search-results.js @@ -32,11 +32,11 @@ function InserterSearchResults( { rootClientId, clientId, isAppender, - selectBlockOnInsert, maxBlockPatterns, maxBlockTypes, showBlockDirectory = false, isDraggable = true, + shouldFocusBlock = true, } ) { const debouncedSpeak = useDebounce( speak, 500 ); @@ -45,7 +45,7 @@ function InserterSearchResults( { rootClientId, clientId, isAppender, - selectBlockOnInsert, + shouldFocusBlock, } ); const [ blockTypes, diff --git a/packages/block-editor/src/components/provider/test/use-block-sync.js b/packages/block-editor/src/components/provider/test/use-block-sync.js index 18e6d3e4b6e03..c45b1e02742f9 100644 --- a/packages/block-editor/src/components/provider/test/use-block-sync.js +++ b/packages/block-editor/src/components/provider/test/use-block-sync.js @@ -256,7 +256,13 @@ describe( 'useBlockSync hook', () => { expect( onInput ).toHaveBeenCalledWith( [ { clientId: 'a', innerBlocks: [], attributes: { foo: 2 } } ], - { selectionEnd: {}, selectionStart: {} } + { + selection: { + selectionEnd: {}, + selectionStart: {}, + initialPosition: null, + }, + } ); expect( onChange ).not.toHaveBeenCalled(); } ); @@ -292,7 +298,13 @@ describe( 'useBlockSync hook', () => { expect( onChange ).toHaveBeenCalledWith( [ { clientId: 'a', innerBlocks: [], attributes: { foo: 2 } } ], - { selectionEnd: {}, selectionStart: {} } + { + selection: { + selectionEnd: {}, + selectionStart: {}, + initialPosition: null, + }, + } ); expect( onInput ).not.toHaveBeenCalled(); } ); @@ -395,7 +407,13 @@ describe( 'useBlockSync hook', () => { attributes: { foo: 2 }, }, ], - { selectionEnd: {}, selectionStart: {} } + { + selection: { + selectionEnd: {}, + selectionStart: {}, + initialPosition: null, + }, + } ); expect( onInput ).not.toHaveBeenCalled(); } ); @@ -433,8 +451,11 @@ describe( 'useBlockSync hook', () => { ]; expect( onChange1 ).toHaveBeenCalledWith( updatedBlocks1, { - selectionEnd: {}, - selectionStart: {}, + selection: { + initialPosition: null, + selectionEnd: {}, + selectionStart: {}, + }, } ); const newBlocks = [ @@ -469,7 +490,13 @@ describe( 'useBlockSync hook', () => { // The second callback should be called with the new change. expect( onChange2 ).toHaveBeenCalledWith( [ { clientId: 'b', innerBlocks: [], attributes: { foo: 3 } } ], - { selectionEnd: {}, selectionStart: {} } + { + selection: { + selectionEnd: {}, + selectionStart: {}, + initialPosition: null, + }, + } ); } ); @@ -526,7 +553,13 @@ describe( 'useBlockSync hook', () => { // Only the new callback should be called. expect( onChange2 ).toHaveBeenCalledWith( [ { clientId: 'b', innerBlocks: [], attributes: { foo: 3 } } ], - { selectionEnd: {}, selectionStart: {} } + { + selection: { + selectionEnd: {}, + selectionStart: {}, + initialPosition: null, + }, + } ); } ); } ); diff --git a/packages/block-editor/src/components/provider/use-block-sync.js b/packages/block-editor/src/components/provider/use-block-sync.js index ff28953513c6e..07a6a147b335f 100644 --- a/packages/block-editor/src/components/provider/use-block-sync.js +++ b/packages/block-editor/src/components/provider/use-block-sync.js @@ -55,10 +55,7 @@ import { store as blockEditorStore } from '../../store'; * is used to initalize the block-editor store * and for resetting the blocks to incoming * changes like undo. - * @param {Object} props.selectionStart The selection start vlaue from the - * controlling component. - * @param {Object} props.selectionEnd The selection end vlaue from the - * controlling component. + * @param {Object} props.selection The selection state responsible to restore the selection on undo/redo. * @param {onBlockUpdate} props.onChange Function to call when a persistent * change has been made in the block-editor blocks * for the given clientId. For example, after @@ -72,8 +69,7 @@ import { store as blockEditorStore } from '../../store'; export default function useBlockSync( { clientId = null, value: controlledBlocks, - selectionStart: controlledSelectionStart, - selectionEnd: controlledSelectionEnd, + selection: controlledSelection, onChange = noop, onInput = noop, } ) { @@ -151,10 +147,11 @@ export default function useBlockSync( { pendingChanges.current.outgoing = []; setControlledBlocks(); - if ( controlledSelectionStart && controlledSelectionEnd ) { + if ( controlledSelection ) { resetSelection( - controlledSelectionStart, - controlledSelectionEnd + controlledSelection.selectionStart, + controlledSelection.selectionEnd, + controlledSelection.initialPosition ); } } @@ -164,6 +161,7 @@ export default function useBlockSync( { const { getSelectionStart, getSelectionEnd, + getSelectedBlocksInitialCaretPosition, isLastBlockChangePersistent, __unstableIsLastBlockChangeIgnored, } = registry.select( blockEditorStore ); @@ -223,8 +221,11 @@ export default function useBlockSync( { ? onChangeRef.current : onInputRef.current; updateParent( blocks, { - selectionStart: getSelectionStart(), - selectionEnd: getSelectionEnd(), + selection: { + selectionStart: getSelectionStart(), + selectionEnd: getSelectionEnd(), + initialPosition: getSelectedBlocksInitialCaretPosition(), + }, } ); } previousAreBlocksDifferent = areBlocksDifferent; diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index ad34bf4c0c82b..a6c0066647ecd 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -345,7 +345,7 @@ function RichTextWrapper( // If there are pasted blocks, move the caret to the end of the selected block // Otherwise, retain the default value. - const initialPosition = hasPastedBlocks ? -1 : null; + const initialPosition = hasPastedBlocks ? -1 : 0; onReplace( blocks, indexToSelect, initialPosition ); }, diff --git a/packages/block-editor/src/components/multi-select-scroll-into-view/index.js b/packages/block-editor/src/components/selection-scroll-into-view/index.js similarity index 69% rename from packages/block-editor/src/components/multi-select-scroll-into-view/index.js rename to packages/block-editor/src/components/selection-scroll-into-view/index.js index e60d7798f0e78..5e9ff07f1ca6d 100644 --- a/packages/block-editor/src/components/multi-select-scroll-into-view/index.js +++ b/packages/block-editor/src/components/selection-scroll-into-view/index.js @@ -16,26 +16,11 @@ import { getScrollContainer } from '@wordpress/dom'; import { getBlockDOMNode } from '../../utils/dom'; import { store as blockEditorStore } from '../../store'; -export function useScrollMultiSelectionIntoView( ref ) { - const selectionEnd = useSelect( ( select ) => { - const { - getBlockSelectionEnd, - hasMultiSelection, - isMultiSelecting, - } = select( blockEditorStore ); - - const blockSelectionEnd = getBlockSelectionEnd(); - - if ( - ! blockSelectionEnd || - isMultiSelecting() || - ! hasMultiSelection() - ) { - return; - } - - return blockSelectionEnd; - }, [] ); +export function useScrollSelectionIntoView( ref ) { + const selectionEnd = useSelect( + ( select ) => select( blockEditorStore ).getBlockSelectionEnd(), + [] + ); useEffect( () => { if ( ! selectionEnd ) { @@ -67,8 +52,8 @@ export function useScrollMultiSelectionIntoView( ref ) { * Scrolls the multi block selection end into view if not in view already. This * is important to do after selection by keyboard. */ -export default function MultiSelectScrollIntoView() { +export function MultiSelectScrollIntoView() { const ref = useRef(); - useScrollMultiSelectionIntoView( ref ); + useScrollSelectionIntoView( ref ); return
; } diff --git a/packages/block-editor/src/components/use-on-block-drop/index.js b/packages/block-editor/src/components/use-on-block-drop/index.js index 932d94d81b47f..f2ee0ee1c0182 100644 --- a/packages/block-editor/src/components/use-on-block-drop/index.js +++ b/packages/block-editor/src/components/use-on-block-drop/index.js @@ -79,7 +79,13 @@ export function onBlockDrop( // If the user is inserting a block if ( dropType === 'inserter' ) { clearSelectedBlock(); - insertBlocks( blocks, targetBlockIndex, targetRootClientId, false ); + insertBlocks( + blocks, + targetBlockIndex, + targetRootClientId, + true, + null + ); } // If the user is moving a block diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 82e69c2c5d1f7..1ad76cc84146d 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { castArray, findKey, first, last, some } from 'lodash'; +import { castArray, findKey, first, isObject, last, some } from 'lodash'; /** * WordPress dependencies @@ -20,6 +20,7 @@ import { speak } from '@wordpress/a11y'; import { __, _n, sprintf } from '@wordpress/i18n'; import { controls } from '@wordpress/data'; import { create, insert, remove, toHTMLString } from '@wordpress/rich-text'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -113,16 +114,22 @@ export function* validateBlocksToTemplate( blocks ) { * Returns an action object used in signalling that selection state should be * reset to the specified selection. * - * @param {WPBlockSelection} selectionStart The selection start. - * @param {WPBlockSelection} selectionEnd The selection end. + * @param {WPBlockSelection} selectionStart The selection start. + * @param {WPBlockSelection} selectionEnd The selection end. + * @param {0|-1|null} initialPosition Initial block position. * * @return {Object} Action object. */ -export function resetSelection( selectionStart, selectionEnd ) { +export function resetSelection( + selectionStart, + selectionEnd, + initialPosition +) { return { type: 'RESET_SELECTION', selectionStart, selectionEnd, + initialPosition, }; } @@ -182,13 +189,13 @@ export function updateBlock( clientId, updates ) { * value reflecting its selection directionality. An initialPosition of -1 * reflects a reverse selection. * - * @param {string} clientId Block client ID. - * @param {?number} initialPosition Optional initial position. Pass as -1 to + * @param {string} clientId Block client ID. + * @param {0|-1|null} initialPosition Optional initial position. Pass as -1 to * reflect reverse selection. * * @return {Object} Action object. */ -export function selectBlock( clientId, initialPosition = null ) { +export function selectBlock( clientId, initialPosition = 0 ) { return { type: 'SELECT_BLOCK', initialPosition, @@ -347,7 +354,7 @@ function getBlocksWithDefaultStylesApplied( blocks, blockEditorSettings ) { * @param {(string|string[])} clientIds Block client ID(s) to replace. * @param {(Object|Object[])} blocks Replacement block(s). * @param {number} indexToSelect Index of replacement block to select. - * @param {number} initialPosition Index of caret after in the selected block after the operation. + * @param {0|-1|null} initialPosition Index of caret after in the selected block after the operation. * @param {?Object} meta Optional Meta values to be passed to the action object. * * @yield {Object} Action object. @@ -356,7 +363,7 @@ export function* replaceBlocks( clientIds, blocks, indexToSelect, - initialPosition, + initialPosition = 0, meta ) { clientIds = castArray( clientIds ); @@ -540,21 +547,30 @@ export function insertBlock( * Returns an action object used in signalling that an array of blocks should * be inserted, optionally at a specific index respective a root block list. * - * @param {Object[]} blocks Block objects to insert. - * @param {?number} index Index at which block should be inserted. - * @param {?string} rootClientId Optional root client ID of block list on which to insert. - * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to true. - * @param {?Object} meta Optional Meta values to be passed to the action object. - * - * @return {Object} Action object. + * @param {Object[]} blocks Block objects to insert. + * @param {?number} index Index at which block should be inserted. + * @param {?string} rootClientId Optional root client ID of block list on which to insert. + * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to true. + * @param {0|-1|null} initialPosition Initial focus position. Setting it to null prevent focusing the inserted block. + * @param {?Object} meta Optional Meta values to be passed to the action object. + * @return {Object} Action object. */ export function* insertBlocks( blocks, index, rootClientId, updateSelection = true, + initialPosition = 0, meta ) { + if ( isObject( initialPosition ) ) { + meta = initialPosition; + initialPosition = 0; + deprecated( "meta argument in wp.data.dispatch('core/block-editor')", { + hint: 'The meta argument is now the 6th argument of the function', + } ); + } + blocks = getBlocksWithDefaultStylesApplied( castArray( blocks ), yield controls.select( blockEditorStoreName, 'getSettings' ) @@ -579,6 +595,7 @@ export function* insertBlocks( rootClientId, time: Date.now(), updateSelection, + initialPosition: updateSelection ? initialPosition : null, meta, }; } @@ -902,22 +919,24 @@ export function removeBlock( clientId, selectPrevious ) { * Returns an action object used in signalling that the inner blocks with the * specified client ID should be replaced. * - * @param {string} rootClientId Client ID of the block whose InnerBlocks will re replaced. - * @param {Object[]} blocks Block objects to insert as new InnerBlocks - * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to false. - * + * @param {string} rootClientId Client ID of the block whose InnerBlocks will re replaced. + * @param {Object[]} blocks Block objects to insert as new InnerBlocks + * @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to false. + * @param {0|-1|null} initialPosition Initial block position. * @return {Object} Action object. */ export function replaceInnerBlocks( rootClientId, blocks, - updateSelection = false + updateSelection = false, + initialPosition = 0 ) { return { type: 'REPLACE_INNER_BLOCKS', rootClientId, blocks, updateSelection, + initialPosition: updateSelection ? initialPosition : null, time: Date.now(), }; } diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 5950a7bfc7ef8..2b14fa612c96f 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1189,9 +1189,8 @@ function selectionHelper( state = {}, action ) { } return { clientId: action.clientId }; - case 'REPLACE_INNER_BLOCKS': // REPLACE_INNER_BLOCKS and INSERT_BLOCKS should follow the same logic. + case 'REPLACE_INNER_BLOCKS': case 'INSERT_BLOCKS': { - // REPLACE_INNER_BLOCKS can be called with an empty array. if ( ! action.updateSelection || ! action.blocks.length ) { return state; } @@ -1225,11 +1224,7 @@ function selectionHelper( state = {}, action ) { return state; } - const newState = { clientId: blockToSelect.clientId }; - if ( typeof action.initialPosition === 'number' ) { - newState.initialPosition = action.initialPosition; - } - return newState; + return { clientId: blockToSelect.clientId }; } } @@ -1358,23 +1353,26 @@ export function isSelectionEnabled( state = true, action ) { * @param {boolean} state Current state. * @param {Object} action Dispatched action. * - * @return {?number} Initial position: -1 or undefined. + * @return {number|null} Initial position: 0, -1 or null. */ -export function initialPosition( state, action ) { +export function initialPosition( state = null, action ) { if ( action.type === 'REPLACE_BLOCKS' && - typeof action.initialPosition === 'number' + action.initialPosition !== undefined ) { return action.initialPosition; - } else if ( action.type === 'SELECT_BLOCK' ) { + } else if ( + [ + 'SELECT_BLOCK', + 'RESET_SELECTION', + 'INSERT_BLOCKS', + 'REPLACE_INNER_BLOCKS', + ].includes( action.type ) + ) { return action.initialPosition; - } else if ( action.type === 'REMOVE_BLOCKS' ) { - return state; - } else if ( action.type === 'START_TYPING' ) { - return state; } - // Reset the state by default (for any action not handled). + return state; } export function blocksMode( state = {}, action ) { diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index e338ee258fd7f..ae383dff60331 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -692,10 +692,11 @@ export function getNextBlockClientId( state, startClientId ) { /** * Returns the initial caret position for the selected block. * This position is to used to position the caret properly when the selected block changes. + * If the current block is not a RichText, having initial position set to 0 means "focus block" * * @param {Object} state Global application state. * - * @return {?Object} Selected block. + * @return {0|-1|null} Initial position. */ export function getSelectedBlocksInitialCaretPosition( state ) { return state.initialPosition; diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js index 2e831c1f771c5..0206db8dbc960 100644 --- a/packages/block-editor/src/store/test/actions.js +++ b/packages/block-editor/src/store/test/actions.js @@ -200,6 +200,7 @@ describe( 'actions', () => { clientIds: [ 'chicken' ], blocks: [ block ], time: expect.any( Number ), + initialPosition: 0, } ); expect( replaceBlockGenerator.next().value ).toEqual( @@ -318,6 +319,7 @@ describe( 'actions', () => { clientIds: [ 'chicken' ], blocks, time: expect.any( Number ), + initialPosition: 0, } ); expect( replaceBlockGenerator.next().value ).toEqual( @@ -406,6 +408,7 @@ describe( 'actions', () => { rootClientId: 'testclientid', time: expect.any( Number ), updateSelection: true, + initialPosition: 0, }, } ); } ); @@ -493,6 +496,7 @@ describe( 'actions', () => { rootClientId: 'testrootid', time: expect.any( Number ), updateSelection: false, + initialPosition: null, }, } ); } ); @@ -549,6 +553,7 @@ describe( 'actions', () => { rootClientId: 'testrootid', time: expect.any( Number ), updateSelection: false, + initialPosition: null, }, } ); } ); @@ -613,6 +618,7 @@ describe( 'actions', () => { rootClientId: 'testrootid', time: expect.any( Number ), updateSelection: false, + initialPosition: null, }, } ); } ); @@ -683,6 +689,7 @@ describe( 'actions', () => { 5, 'testrootid', false, + 0, meta ); @@ -725,6 +732,7 @@ describe( 'actions', () => { rootClientId: 'testrootid', time: expect.any( Number ), updateSelection: false, + initialPosition: null, meta: { patternName: 'core/chicken-ribs-pattern' }, }, } ); @@ -1140,6 +1148,7 @@ describe( 'actions', () => { rootClientId: 'root', time: expect.any( Number ), updateSelection: false, + initialPosition: null, } ); } ); @@ -1150,6 +1159,7 @@ describe( 'actions', () => { rootClientId: 'root', time: expect.any( Number ), updateSelection: true, + initialPosition: 0, } ); } ); } ); diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index b02b9260c0311..95e574fb69f95 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -2301,22 +2301,6 @@ describe( 'state', () => { expect( state.selectionEnd ).toEqual( expected ); } ); - it( 'should not select inserted block if updateSelection flag is false', () => { - const original = deepFreeze( { - selectionStart: { clientId: 'a' }, - selectionEnd: { clientId: 'a' }, - } ); - const action = { - type: 'INSERT_BLOCKS', - blocks: [ { clientId: 'ribs' } ], - updateSelection: false, - }; - const state = selection( original, action ); - - expect( state.selectionStart ).toBe( original.selectionStart ); - expect( state.selectionEnd ).toBe( original.selectionEnd ); - } ); - it( 'should not update the state if the block moved is already selected', () => { const original = deepFreeze( { selectionStart: { clientId: 'ribs' }, @@ -2475,23 +2459,6 @@ describe( 'state', () => { expect( state.selectionEnd ).toEqual( expected.selectionEnd ); } ); - it( 'should not update the selection on inner blocks replace if updateSelection is false', () => { - const original = deepFreeze( { - selectionStart: { clientId: 'chicken' }, - selectionEnd: { clientId: 'chicken' }, - } ); - const action = { - type: 'REPLACE_INNER_BLOCKS', - blocks: [ { clientId: 'another-block' } ], - rootClientId: 'parent', - updateSelection: false, - }; - const state = selection( original, action ); - - expect( state.selectionStart ).toEqual( original.selectionStart ); - expect( state.selectionEnd ).toEqual( original.selectionEnd ); - } ); - it( 'should keep the same selection on RESET_BLOCKS if the selected blocks continue to exist', () => { const original = deepFreeze( { selectionStart: { clientId: 'chicken' }, diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index c711f191a7c9e..6d3b0929a4614 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -162,8 +162,7 @@ function* loadPostTypeEntities() { label: postType.labels.singular_name, transientEdits: { blocks: true, - selectionStart: true, - selectionEnd: true, + selection: true, }, mergedEdits: { meta: true }, getTitle: ( record ) => diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index 1c772014a4051..5957848ee66d7 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -172,8 +172,8 @@ export function useEntityBlockEditor( kind, type, { id: _id } = {} ) { const onChange = useCallback( ( newBlocks, options ) => { - const { selectionStart, selectionEnd } = options; - const edits = { blocks: newBlocks, selectionStart, selectionEnd }; + const { selection } = options; + const edits = { blocks: newBlocks, selection }; const noChange = blocks === edits.blocks; if ( noChange ) { @@ -193,8 +193,8 @@ export function useEntityBlockEditor( kind, type, { id: _id } = {} ) { const onInput = useCallback( ( newBlocks, options ) => { - const { selectionStart, selectionEnd } = options; - const edits = { blocks: newBlocks, selectionStart, selectionEnd }; + const { selection } = options; + const edits = { blocks: newBlocks, selection }; editEntityRecord( kind, type, id, edits ); }, [ kind, type, id ] diff --git a/packages/e2e-test-utils/src/inserter.js b/packages/e2e-test-utils/src/inserter.js index 33ffa10a7c293..29a5d5d844409 100644 --- a/packages/e2e-test-utils/src/inserter.js +++ b/packages/e2e-test-utils/src/inserter.js @@ -54,6 +54,23 @@ export async function toggleGlobalBlockInserter() { ); } +/** + * Moves focus to the selected block. + */ +async function focusSelectedBlock() { + // Ideally there shouuld be a UI way to do this. (Focus the selected block) + await page.evaluate( () => { + wp.data + .dispatch( 'core/block-editor' ) + .selectBlock( + wp.data + .select( 'core/block-editor' ) + .getSelectedBlockClientId(), + 0 + ); + } ); +} + /** * Retrieves the document container by css class and checks to make sure the document's active element is within it */ @@ -135,6 +152,7 @@ export async function insertBlock( searchTerm ) { `//button//span[contains(text(), '${ searchTerm }')]` ); await insertButton.click(); + await focusSelectedBlock(); // We should wait until the inserter closes and the focus moves to the content. await waitForInserterCloseAndContentFocus(); } @@ -151,6 +169,7 @@ export async function insertPattern( searchTerm ) { `//div[@role = 'option']//div[contains(text(), '${ searchTerm }')]` ); await insertButton.click(); + await focusSelectedBlock(); // We should wait until the inserter closes and the focus moves to the content. await waitForInserterCloseAndContentFocus(); } @@ -168,6 +187,7 @@ export async function insertReusableBlock( searchTerm ) { `//button//span[contains(text(), '${ searchTerm }')]` ); await insertButton.click(); + await focusSelectedBlock(); // We should wait until the inserter closes and the focus moves to the content. await waitForInserterCloseAndContentFocus(); // We should wait until the block is loaded @@ -191,6 +211,13 @@ export async function insertBlockDirectoryBlock( searchTerm ) { '.block-directory-downloadable-blocks-list li:first-child button' ); await insertButton.click(); + await page.waitForFunction( + () => + ! document.body.querySelector( + '.block-directory-downloadable-blocks-list li:first-child button.is-busy' + ) + ); + await focusSelectedBlock(); // We should wait until the inserter closes and the focus moves to the content. await waitForInserterCloseAndContentFocus(); } diff --git a/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js index 65c94104b80c7..a3432bd03a160 100644 --- a/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js +++ b/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js @@ -75,6 +75,11 @@ describe( 'Allowed Blocks Setting on InnerBlocks', () => { await insertBlock( 'Image' ); await closeGlobalBlockInserter(); await page.waitForSelector( '.product[data-number-of-children="2"]' ); + // This focus shouldn't be neessary but there's a bug in master right now + // Where if you open the inserter, don't do anything and click the "appender" on the canvas + // the appender is not opened right away. + // It needs to be investigated on its own. + await page.focus( appenderSelector ); await page.click( appenderSelector ); expect( await getAllBlockInserterItemTitles() ).toEqual( [ 'Gallery', 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 cb6698bb3df4c..af2fb84006fe4 100644 --- a/packages/e2e-tests/specs/editor/various/writing-flow.test.js +++ b/packages/e2e-tests/specs/editor/various/writing-flow.test.js @@ -558,6 +558,11 @@ describe( 'Writing Flow', () => { await page.keyboard.press( 'Enter' ); await clickBlockToolbarButton( 'Align' ); await clickButton( 'Wide width' ); + + // Focus the block. + await page.keyboard.press( 'Tab' ); + + // Select the previous block. await page.keyboard.press( 'ArrowUp' ); // Confirm correct setup. diff --git a/packages/e2e-tests/specs/widgets/adding-widgets.test.js b/packages/e2e-tests/specs/widgets/adding-widgets.test.js index 27f1c046dfc51..6d9b6ba37c822 100644 --- a/packages/e2e-tests/specs/widgets/adding-widgets.test.js +++ b/packages/e2e-tests/specs/widgets/adding-widgets.test.js @@ -6,6 +6,7 @@ import { deactivatePlugin, activatePlugin, activateTheme, + pressKeyWithModifier, } from '@wordpress/e2e-test-utils'; /** @@ -105,7 +106,8 @@ describe( 'Widgets screen', () => { // firstWidgetArea // ); - await addParagraphBlock.click(); + await addParagraphBlock.focus(); + await pressKeyWithModifier( 'primary', 'Enter' ); const addedParagraphBlockInFirstWidgetArea = await firstWidgetArea.$( '[data-block][data-type="core/paragraph"][aria-label^="Empty block"]' @@ -125,7 +127,8 @@ describe( 'Widgets screen', () => { await expectInsertionPointIndicatorToBeBelowLastBlock( firstWidgetArea ); - await addParagraphBlock.click(); + await addParagraphBlock.focus(); + await pressKeyWithModifier( 'primary', 'Enter' ); await page.keyboard.type( 'Second Paragraph' ); @@ -311,7 +314,8 @@ describe( 'Widgets screen', () => { ); const addParagraphBlock = await getParagraphBlockInGlobalInserter(); - await addParagraphBlock.click(); + await addParagraphBlock.focus(); + await pressKeyWithModifier( 'primary', 'Enter' ); const firstParagraphBlock = await firstWidgetArea.$( '[data-block][data-type="core/paragraph"][aria-label^="Empty block"]' diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js index d28ce6fcf122b..dc402828d43d6 100644 --- a/packages/edit-post/src/components/visual-editor/index.js +++ b/packages/edit-post/src/components/visual-editor/index.js @@ -12,7 +12,6 @@ import { __unstableUseTypewriter as useTypewriter, __unstableUseClipboardHandler as useClipboardHandler, __unstableUseTypingObserver as useTypingObserver, - __unstableUseScrollMultiSelectionIntoView as useScrollMultiSelectionIntoView, __experimentalBlockSettingsMenuFirstItem, __experimentalUseResizeCanvas as useResizeCanvas, __unstableUseCanvasClickRedirect as useCanvasClickRedirect, @@ -52,7 +51,6 @@ export default function VisualEditor( { styles } ) { }; const resizedCanvasStyles = useResizeCanvas( deviceType ); - useScrollMultiSelectionIntoView( ref ); useBlockSelectionClearer( ref ); useTypewriter( ref ); useClipboardHandler( ref ); diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index a04ef2f474934..894596819e68a 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -33,16 +33,13 @@ function EditorProvider( { } return { postId: post.id, postType: post.type }; }, [ post.id, post.type ] ); - const { selectionEnd, selectionStart, isReady } = useSelect( ( select ) => { - const { - getEditorSelectionStart, - getEditorSelectionEnd, - __unstableIsEditorReady, - } = select( editorStore ); + const { selection, isReady } = useSelect( ( select ) => { + const { getEditorSelection, __unstableIsEditorReady } = select( + editorStore + ); return { isReady: __unstableIsEditorReady(), - selectionStart: getEditorSelectionStart(), - selectionEnd: getEditorSelectionEnd(), + selection: getEditorSelection(), }; }, [] ); const { id, type } = __unstableTemplate ?? post; @@ -112,8 +109,7 @@ function EditorProvider( { value={ blocks } onChange={ onChange } onInput={ onInput } - selectionStart={ selectionStart } - selectionEnd={ selectionEnd } + selection={ selection } settings={ editorSettings } useSubRegistry={ false } > diff --git a/packages/editor/src/components/provider/index.native.js b/packages/editor/src/components/provider/index.native.js index 76bb230c250bc..d788b1a2447e3 100644 --- a/packages/editor/src/components/provider/index.native.js +++ b/packages/editor/src/components/provider/index.native.js @@ -45,8 +45,7 @@ const postTypeEntities = [ ...postTypeEntity, transientEdits: { blocks: true, - selectionStart: true, - selectionEnd: true, + selection: true, }, mergedEdits: { meta: true, diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 072f0c19e77c8..4bfdf46e81c8c 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -593,12 +593,8 @@ export function unlockPostAutosaving( lockName ) { * @yield {Object} Action object */ export function* resetEditorBlocks( blocks, options = {} ) { - const { - __unstableShouldCreateUndoLevel, - selectionStart, - selectionEnd, - } = options; - const edits = { blocks, selectionStart, selectionEnd }; + const { __unstableShouldCreateUndoLevel, selection } = options; + const edits = { blocks, selection }; if ( __unstableShouldCreateUndoLevel !== false ) { const { id, type } = yield controls.select( diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index b3bf751286809..4323085e77b2f 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1229,9 +1229,14 @@ export function getEditorBlocks( state ) { * * @param {Object} state * @return {WPBlockSelection} The selection start. + * + * @deprecated since Gutenberg 10.0.0. */ export function getEditorSelectionStart( state ) { - return getEditedPostAttribute( state, 'selectionStart' ); + deprecated( "select('core/editor').getEditorSelectionStart", { + alternative: "select('core/editor').getEditorSelection", + } ); + return getEditedPostAttribute( state, 'selection' )?.selectionStart; } /** @@ -1239,9 +1244,24 @@ export function getEditorSelectionStart( state ) { * * @param {Object} state * @return {WPBlockSelection} The selection end. + * + * @deprecated since Gutenberg 10.0.0. */ export function getEditorSelectionEnd( state ) { - return getEditedPostAttribute( state, 'selectionEnd' ); + deprecated( "select('core/editor').getEditorSelectionStart", { + alternative: "select('core/editor').getEditorSelection", + } ); + return getEditedPostAttribute( state, 'selection' )?.selectionEnd; +} + +/** + * Returns the current selection. + * + * @param {Object} state + * @return {WPBlockSelection} The selection end. + */ +export function getEditorSelection( state ) { + return getEditedPostAttribute( state, 'selection' ); } /**