Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List: avoid useSelect in block render #57077

Merged
merged 3 commits into from
Dec 14, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions packages/block-library/src/list-item/edit.js
Original file line number Diff line number Diff line change
@@ -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,24 @@ 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( clientId );
const canIndent = useSelect(
( select ) => select( blockEditorStore ).getBlockIndex( clientId ) > 0,
[ clientId ]
);
const canOutdent = useSelect(
( select ) => {
const { getBlockRootClientId, getBlockName } =
select( blockEditorStore );
return (
getBlockName(
getBlockRootClientId( getBlockRootClientId( clientId ) )
) === 'core/list-item'
);
},
[ clientId ]
);

return (
<>
125 changes: 63 additions & 62 deletions packages/block-library/src/list-item/hooks/use-enter.js
Original file line number Diff line number Diff line change
@@ -19,71 +19,72 @@ import useOutdentListItem from './use-outdent-list-item';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what you did here but I think I copied this one to button block, so you think it can be improved there too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't have a mapping useSelect because there's no outdent/indent

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( 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();
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 );
};
}, [] );
}
14 changes: 9 additions & 5 deletions packages/block-library/src/list-item/hooks/use-enter.native.js
Original file line number Diff line number Diff line change
@@ -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( propsRef.current.clientId );

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;
}
Original file line number Diff line number Diff line change
@@ -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 ] );
}
2 changes: 1 addition & 1 deletion packages/block-library/src/list-item/hooks/use-merge.js
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ export default function useMerge( clientId, onMerge ) {
} = useSelect( blockEditorStore );
const { mergeBlocks, moveBlocksToPosition } =
useDispatch( blockEditorStore );
const [ , outdentListItem ] = useOutdentListItem( clientId );
const outdentListItem = useOutdentListItem( clientId );

function getTrailingId( id ) {
const order = getBlockOrder( id );
Loading