diff --git a/packages/block-library/src/list-item/edit.js b/packages/block-library/src/list-item/edit.js index 3f26840ad345f9..7733a762807528 100644 --- a/packages/block-library/src/list-item/edit.js +++ b/packages/block-library/src/list-item/edit.js @@ -6,6 +6,7 @@ import { useBlockProps, useInnerBlocksProps, BlockControls, + store as blockEditorStore, } from '@wordpress/block-editor'; import { isRTL, __ } from '@wordpress/i18n'; import { ToolbarButton } from '@wordpress/components'; @@ -16,6 +17,7 @@ import { formatIndent, } from '@wordpress/icons'; import { useMergeRefs } from '@wordpress/compose'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -32,8 +34,22 @@ import { import { convertToListItems } from './utils'; export function IndentUI( { clientId } ) { - const [ canIndent, indentListItem ] = useIndentListItem( clientId ); - const [ canOutdent, outdentListItem ] = useOutdentListItem( clientId ); + const indentListItem = useIndentListItem( clientId ); + const outdentListItem = useOutdentListItem(); + const { canIndent, canOutdent } = useSelect( + ( select ) => { + const { getBlockIndex, getBlockRootClientId, getBlockName } = + select( blockEditorStore ); + return { + canIndent: getBlockIndex( clientId ) > 0, + canOutdent: + getBlockName( + getBlockRootClientId( getBlockRootClientId( clientId ) ) + ) === 'core/list-item', + }; + }, + [ clientId ] + ); return ( <> diff --git a/packages/block-library/src/list-item/hooks/use-enter.js b/packages/block-library/src/list-item/hooks/use-enter.js index 46fdc65cecdd7b..ffe5c55fbbed2e 100644 --- a/packages/block-library/src/list-item/hooks/use-enter.js +++ b/packages/block-library/src/list-item/hooks/use-enter.js @@ -19,71 +19,72 @@ import useOutdentListItem from './use-outdent-list-item'; export default function useEnter( props ) { const { replaceBlocks, selectionChange } = useDispatch( blockEditorStore ); - const { getBlock, getBlockRootClientId, getBlockIndex } = + const { getBlock, getBlockRootClientId, getBlockIndex, getBlockName } = useSelect( blockEditorStore ); const propsRef = useRef( props ); propsRef.current = props; - const [ canOutdent, outdentListItem ] = useOutdentListItem( - propsRef.current.clientId - ); - return useRefEffect( - ( element ) => { - function onKeyDown( event ) { - if ( event.defaultPrevented || event.keyCode !== ENTER ) { - return; - } - const { content, clientId } = propsRef.current; - if ( content.length ) { - return; - } - event.preventDefault(); - if ( canOutdent ) { - outdentListItem(); - return; - } - // Here we are in top level list so we need to split. - const topParentListBlock = getBlock( - getBlockRootClientId( clientId ) - ); - const blockIndex = getBlockIndex( clientId ); - const head = cloneBlock( { - ...topParentListBlock, - innerBlocks: topParentListBlock.innerBlocks.slice( - 0, - blockIndex - ), - } ); - const middle = createBlock( getDefaultBlockName() ); - // Last list item might contain a `list` block innerBlock - // In that case append remaining innerBlocks blocks. - const after = [ - ...( topParentListBlock.innerBlocks[ blockIndex ] - .innerBlocks[ 0 ]?.innerBlocks || [] ), - ...topParentListBlock.innerBlocks.slice( blockIndex + 1 ), - ]; - const tail = after.length - ? [ - cloneBlock( { - ...topParentListBlock, - innerBlocks: after, - } ), - ] - : []; - replaceBlocks( - topParentListBlock.clientId, - [ head, middle, ...tail ], - 1 - ); - // We manually change the selection here because we are replacing - // a different block than the selected one. - selectionChange( middle.clientId ); + const outdentListItem = useOutdentListItem(); + return useRefEffect( ( element ) => { + function onKeyDown( event ) { + if ( event.defaultPrevented || event.keyCode !== ENTER ) { + return; } + const { content, clientId } = propsRef.current; + if ( content.length ) { + return; + } + event.preventDefault(); + const canOutdent = + getBlockName( + getBlockRootClientId( + getBlockRootClientId( propsRef.current.clientId ) + ) + ) === 'core/list-item'; + if ( canOutdent ) { + outdentListItem(); + return; + } + // Here we are in top level list so we need to split. + const topParentListBlock = getBlock( + getBlockRootClientId( clientId ) + ); + const blockIndex = getBlockIndex( clientId ); + const head = cloneBlock( { + ...topParentListBlock, + innerBlocks: topParentListBlock.innerBlocks.slice( + 0, + blockIndex + ), + } ); + const middle = createBlock( getDefaultBlockName() ); + // Last list item might contain a `list` block innerBlock + // In that case append remaining innerBlocks blocks. + const after = [ + ...( topParentListBlock.innerBlocks[ blockIndex ] + .innerBlocks[ 0 ]?.innerBlocks || [] ), + ...topParentListBlock.innerBlocks.slice( blockIndex + 1 ), + ]; + const tail = after.length + ? [ + cloneBlock( { + ...topParentListBlock, + innerBlocks: after, + } ), + ] + : []; + replaceBlocks( + topParentListBlock.clientId, + [ head, middle, ...tail ], + 1 + ); + // We manually change the selection here because we are replacing + // a different block than the selected one. + selectionChange( middle.clientId ); + } - element.addEventListener( 'keydown', onKeyDown ); - return () => { - element.removeEventListener( 'keydown', onKeyDown ); - }; - }, - [ canOutdent ] - ); + element.addEventListener( 'keydown', onKeyDown ); + return () => { + element.removeEventListener( 'keydown', onKeyDown ); + }; + }, [] ); } diff --git a/packages/block-library/src/list-item/hooks/use-enter.native.js b/packages/block-library/src/list-item/hooks/use-enter.native.js index d3be5f1ea0e1bb..596dd469d31326 100644 --- a/packages/block-library/src/list-item/hooks/use-enter.native.js +++ b/packages/block-library/src/list-item/hooks/use-enter.native.js @@ -17,13 +17,11 @@ import useOutdentListItem from './use-outdent-list-item'; export default function useEnter( props, preventDefault ) { const { replaceBlocks, selectionChange } = useDispatch( blockEditorStore ); - const { getBlock, getBlockRootClientId, getBlockIndex } = + const { getBlock, getBlockRootClientId, getBlockIndex, getBlockName } = useSelect( blockEditorStore ); const propsRef = useRef( props ); propsRef.current = props; - const [ canOutdent, outdentListItem ] = useOutdentListItem( - propsRef.current.clientId - ); + const outdentListItem = useOutdentListItem(); return { onEnter() { @@ -32,7 +30,13 @@ export default function useEnter( props, preventDefault ) { return; } preventDefault.current = true; - if ( canOutdent ) { + if ( + getBlockName( + getBlockRootClientId( + getBlockRootClientId( propsRef.current.clientId ) + ) + ) === 'core/list-item' + ) { outdentListItem(); return; } diff --git a/packages/block-library/src/list-item/hooks/use-indent-list-item.js b/packages/block-library/src/list-item/hooks/use-indent-list-item.js index b1fd8394be43ce..6eb5d9d73ba658 100644 --- a/packages/block-library/src/list-item/hooks/use-indent-list-item.js +++ b/packages/block-library/src/list-item/hooks/use-indent-list-item.js @@ -7,10 +7,6 @@ import { store as blockEditorStore } from '@wordpress/block-editor'; import { createBlock, cloneBlock } from '@wordpress/blocks'; export default function useIndentListItem( clientId ) { - const canIndent = useSelect( - ( select ) => select( blockEditorStore ).getBlockIndex( clientId ) > 0, - [ clientId ] - ); const { replaceBlocks, selectionChange, multiSelect } = useDispatch( blockEditorStore ); const { @@ -21,55 +17,49 @@ export default function useIndentListItem( clientId ) { hasMultiSelection, getMultiSelectedBlockClientIds, } = useSelect( blockEditorStore ); - return [ - canIndent, - useCallback( () => { - const _hasMultiSelection = hasMultiSelection(); - const clientIds = _hasMultiSelection - ? getMultiSelectedBlockClientIds() - : [ clientId ]; - const clonedBlocks = clientIds.map( ( _clientId ) => - cloneBlock( getBlock( _clientId ) ) - ); - const previousSiblingId = getPreviousBlockClientId( clientId ); - const newListItem = cloneBlock( getBlock( previousSiblingId ) ); - // If the sibling has no innerBlocks, create a new `list` block. - if ( ! newListItem.innerBlocks?.length ) { - newListItem.innerBlocks = [ createBlock( 'core/list' ) ]; - } - // A list item usually has one `list`, but it's possible to have - // more. So we need to preserve the previous `list` blocks and - // merge the new blocks to the last `list`. - newListItem.innerBlocks[ - newListItem.innerBlocks.length - 1 - ].innerBlocks.push( ...clonedBlocks ); + return useCallback( () => { + const _hasMultiSelection = hasMultiSelection(); + const clientIds = _hasMultiSelection + ? getMultiSelectedBlockClientIds() + : [ clientId ]; + const clonedBlocks = clientIds.map( ( _clientId ) => + cloneBlock( getBlock( _clientId ) ) + ); + const previousSiblingId = getPreviousBlockClientId( clientId ); + const newListItem = cloneBlock( getBlock( previousSiblingId ) ); + // If the sibling has no innerBlocks, create a new `list` block. + if ( ! newListItem.innerBlocks?.length ) { + newListItem.innerBlocks = [ createBlock( 'core/list' ) ]; + } + // A list item usually has one `list`, but it's possible to have + // more. So we need to preserve the previous `list` blocks and + // merge the new blocks to the last `list`. + newListItem.innerBlocks[ + newListItem.innerBlocks.length - 1 + ].innerBlocks.push( ...clonedBlocks ); - // We get the selection start/end here, because when - // we replace blocks, the selection is updated too. - const selectionStart = getSelectionStart(); - const selectionEnd = getSelectionEnd(); - // Replace the previous sibling of the block being indented and the indented blocks, - // with a new block whose attributes are equal to the ones of the previous sibling and - // whose descendants are the children of the previous sibling, followed by the indented blocks. - replaceBlocks( - [ previousSiblingId, ...clientIds ], - [ newListItem ] + // We get the selection start/end here, because when + // we replace blocks, the selection is updated too. + const selectionStart = getSelectionStart(); + const selectionEnd = getSelectionEnd(); + // Replace the previous sibling of the block being indented and the indented blocks, + // with a new block whose attributes are equal to the ones of the previous sibling and + // whose descendants are the children of the previous sibling, followed by the indented blocks. + replaceBlocks( [ previousSiblingId, ...clientIds ], [ newListItem ] ); + if ( ! _hasMultiSelection ) { + selectionChange( + clonedBlocks[ 0 ].clientId, + selectionEnd.attributeKey, + selectionEnd.clientId === selectionStart.clientId + ? selectionStart.offset + : selectionEnd.offset, + selectionEnd.offset + ); + } else { + multiSelect( + clonedBlocks[ 0 ].clientId, + clonedBlocks[ clonedBlocks.length - 1 ].clientId ); - if ( ! _hasMultiSelection ) { - selectionChange( - clonedBlocks[ 0 ].clientId, - selectionEnd.attributeKey, - selectionEnd.clientId === selectionStart.clientId - ? selectionStart.offset - : selectionEnd.offset, - selectionEnd.offset - ); - } else { - multiSelect( - clonedBlocks[ 0 ].clientId, - clonedBlocks[ clonedBlocks.length - 1 ].clientId - ); - } - }, [ clientId ] ), - ]; + } + }, [ clientId ] ); } diff --git a/packages/block-library/src/list-item/hooks/use-merge.js b/packages/block-library/src/list-item/hooks/use-merge.js index cda1f0c02d3a88..2fbee4ba275a12 100644 --- a/packages/block-library/src/list-item/hooks/use-merge.js +++ b/packages/block-library/src/list-item/hooks/use-merge.js @@ -20,7 +20,7 @@ export default function useMerge( clientId, onMerge ) { } = useSelect( blockEditorStore ); const { mergeBlocks, moveBlocksToPosition } = useDispatch( blockEditorStore ); - const [ , outdentListItem ] = useOutdentListItem( clientId ); + const outdentListItem = useOutdentListItem(); function getTrailingId( id ) { const order = getBlockOrder( id ); diff --git a/packages/block-library/src/list-item/hooks/use-outdent-list-item.js b/packages/block-library/src/list-item/hooks/use-outdent-list-item.js index 14598dc7451cfa..85c433fbeffada 100644 --- a/packages/block-library/src/list-item/hooks/use-outdent-list-item.js +++ b/packages/block-library/src/list-item/hooks/use-outdent-list-item.js @@ -6,24 +6,8 @@ import { useSelect, useDispatch, useRegistry } from '@wordpress/data'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { cloneBlock } from '@wordpress/blocks'; -export default function useOutdentListItem( clientId ) { +export default function useOutdentListItem() { const registry = useRegistry(); - const { canOutdent } = useSelect( - ( innerSelect ) => { - const { getBlockRootClientId, getBlockName } = - innerSelect( blockEditorStore ); - const grandParentId = getBlockRootClientId( - getBlockRootClientId( clientId ) - ); - const grandParentName = getBlockName( grandParentId ); - const isListItem = grandParentName === 'core/list-item'; - - return { - canOutdent: isListItem, - }; - }, - [ clientId ] - ); const { moveBlocksToPosition, removeBlock, @@ -48,69 +32,66 @@ export default function useOutdentListItem( clientId ) { return parentListItemId; } - return [ - canOutdent, - useCallback( ( clientIds = getSelectedBlockClientIds() ) => { - if ( ! Array.isArray( clientIds ) ) { - clientIds = [ clientIds ]; - } - - if ( ! clientIds.length ) return; + return useCallback( ( clientIds = getSelectedBlockClientIds() ) => { + if ( ! Array.isArray( clientIds ) ) { + clientIds = [ clientIds ]; + } - const firstClientId = clientIds[ 0 ]; + if ( ! clientIds.length ) return; - // Can't outdent if it's not a list item. - if ( getBlockName( firstClientId ) !== 'core/list-item' ) return; + const firstClientId = clientIds[ 0 ]; - const parentListItemId = getParentListItemId( firstClientId ); + // Can't outdent if it's not a list item. + if ( getBlockName( firstClientId ) !== 'core/list-item' ) return; - // Can't outdent if it's at the top level. - if ( ! parentListItemId ) return; + const parentListItemId = getParentListItemId( firstClientId ); - const parentListId = getBlockRootClientId( firstClientId ); - const lastClientId = clientIds[ clientIds.length - 1 ]; - const order = getBlockOrder( parentListId ); - const followingListItems = order.slice( - getBlockIndex( lastClientId ) + 1 - ); + // Can't outdent if it's at the top level. + if ( ! parentListItemId ) return; - registry.batch( () => { - if ( followingListItems.length ) { - let nestedListId = getBlockOrder( firstClientId )[ 0 ]; + const parentListId = getBlockRootClientId( firstClientId ); + const lastClientId = clientIds[ clientIds.length - 1 ]; + const order = getBlockOrder( parentListId ); + const followingListItems = order.slice( + getBlockIndex( lastClientId ) + 1 + ); - if ( ! nestedListId ) { - const nestedListBlock = cloneBlock( - getBlock( parentListId ), - {}, - [] - ); - nestedListId = nestedListBlock.clientId; - insertBlock( nestedListBlock, 0, firstClientId, false ); - // Immediately update the block list settings, otherwise - // blocks can't be moved here due to canInsert checks. - updateBlockListSettings( - nestedListId, - getBlockListSettings( parentListId ) - ); - } + registry.batch( () => { + if ( followingListItems.length ) { + let nestedListId = getBlockOrder( firstClientId )[ 0 ]; - moveBlocksToPosition( - followingListItems, - parentListId, - nestedListId + if ( ! nestedListId ) { + const nestedListBlock = cloneBlock( + getBlock( parentListId ), + {}, + [] + ); + nestedListId = nestedListBlock.clientId; + insertBlock( nestedListBlock, 0, firstClientId, false ); + // Immediately update the block list settings, otherwise + // blocks can't be moved here due to canInsert checks. + updateBlockListSettings( + nestedListId, + getBlockListSettings( parentListId ) ); } + moveBlocksToPosition( - clientIds, + followingListItems, parentListId, - getBlockRootClientId( parentListItemId ), - getBlockIndex( parentListItemId ) + 1 + nestedListId ); - if ( ! getBlockOrder( parentListId ).length ) { - const shouldSelectParent = false; - removeBlock( parentListId, shouldSelectParent ); - } - } ); - }, [] ), - ]; + } + moveBlocksToPosition( + clientIds, + parentListId, + getBlockRootClientId( parentListItemId ), + getBlockIndex( parentListItemId ) + 1 + ); + if ( ! getBlockOrder( parentListId ).length ) { + const shouldSelectParent = false; + removeBlock( parentListId, shouldSelectParent ); + } + } ); + }, [] ); } diff --git a/packages/block-library/src/list-item/hooks/use-space.js b/packages/block-library/src/list-item/hooks/use-space.js index 6079b2c5edb281..deb6313e4b1b0e 100644 --- a/packages/block-library/src/list-item/hooks/use-space.js +++ b/packages/block-library/src/list-item/hooks/use-space.js @@ -12,9 +12,9 @@ import { useSelect } from '@wordpress/data'; import useIndentListItem from './use-indent-list-item'; export default function useSpace( clientId ) { - const { getSelectionStart, getSelectionEnd } = + const { getSelectionStart, getSelectionEnd, getBlockIndex } = useSelect( blockEditorStore ); - const [ canIndent, indentListItem ] = useIndentListItem( clientId ); + const indentListItem = useIndentListItem( clientId ); return useRefEffect( ( element ) => { @@ -23,7 +23,6 @@ export default function useSpace( clientId ) { if ( event.defaultPrevented || - ! canIndent || keyCode !== SPACE || // Only override when no modifiers are pressed. shiftKey || @@ -34,6 +33,10 @@ export default function useSpace( clientId ) { return; } + if ( getBlockIndex( clientId ) === 0 ) { + return; + } + const selectionStart = getSelectionStart(); const selectionEnd = getSelectionEnd(); if ( @@ -50,6 +53,6 @@ export default function useSpace( clientId ) { element.removeEventListener( 'keydown', onKeyDown ); }; }, - [ canIndent, indentListItem ] + [ clientId, indentListItem ] ); } diff --git a/packages/block-library/src/list/edit.js b/packages/block-library/src/list/edit.js index 569e4182b3ea55..e1d29d517a5ffe 100644 --- a/packages/block-library/src/list/edit.js +++ b/packages/block-library/src/list/edit.js @@ -68,48 +68,40 @@ function useMigrateOnLoad( attributes, clientId ) { } function useOutdentList( clientId ) { - const { canOutdent } = useSelect( - ( innerSelect ) => { - const { getBlockRootClientId, getBlock } = - innerSelect( blockEditorStore ); - const parentId = getBlockRootClientId( clientId ); - return { - canOutdent: - !! parentId && - getBlock( parentId ).name === 'core/list-item', - }; - }, - [ clientId ] - ); const { replaceBlocks, selectionChange } = useDispatch( blockEditorStore ); const { getBlockRootClientId, getBlockAttributes, getBlock } = useSelect( blockEditorStore ); - return [ - canOutdent, - useCallback( () => { - const parentBlockId = getBlockRootClientId( clientId ); - const parentBlockAttributes = getBlockAttributes( parentBlockId ); - // Create a new parent block without the inner blocks. - const newParentBlock = createBlock( - 'core/list-item', - parentBlockAttributes - ); - const { innerBlocks } = getBlock( clientId ); - // Replace the parent block with a new parent block without inner blocks, - // and make the inner blocks siblings of the parent. - replaceBlocks( - [ parentBlockId ], - [ newParentBlock, ...innerBlocks ] - ); - // Select the last child of the list being outdent. - selectionChange( innerBlocks[ innerBlocks.length - 1 ].clientId ); - }, [ clientId ] ), - ]; + return useCallback( () => { + const parentBlockId = getBlockRootClientId( clientId ); + const parentBlockAttributes = getBlockAttributes( parentBlockId ); + // Create a new parent block without the inner blocks. + const newParentBlock = createBlock( + 'core/list-item', + parentBlockAttributes + ); + const { innerBlocks } = getBlock( clientId ); + // Replace the parent block with a new parent block without inner blocks, + // and make the inner blocks siblings of the parent. + replaceBlocks( [ parentBlockId ], [ newParentBlock, ...innerBlocks ] ); + // Select the last child of the list being outdent. + selectionChange( innerBlocks[ innerBlocks.length - 1 ].clientId ); + }, [ clientId ] ); } function IndentUI( { clientId } ) { - const [ canOutdent, outdentList ] = useOutdentList( clientId ); + const outdentList = useOutdentList( clientId ); + const canOutdent = useSelect( + ( select ) => { + const { getBlockRootClientId, getBlockName } = + select( blockEditorStore ); + return ( + getBlockName( getBlockRootClientId( clientId ) ) === + 'core/list-item' + ); + }, + [ clientId ] + ); return ( <>