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
Show file tree
Hide file tree
Changes from all commits
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
20 changes: 18 additions & 2 deletions packages/block-library/src/list-item/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -16,6 +17,7 @@ import {
formatIndent,
} from '@wordpress/icons';
import { useMergeRefs } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
Expand All @@ -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 ]
ellatrix marked this conversation as resolved.
Show resolved Hide resolved
);

return (
<>
Expand Down
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
Expand Up @@ -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();
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
Expand Up @@ -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() {
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Up @@ -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 );
Expand Down
Loading
Loading