@wordpress/block-editor
Version:
240 lines (221 loc) • 6.4 kB
JavaScript
/**
* WordPress dependencies
*/
import {
__experimentalTreeGridRow as TreeGridRow,
__experimentalTreeGridCell as TreeGridCell,
} from '@wordpress/components';
import { memo } from '@wordpress/element';
import { AsyncModeProvider, useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import { Appender } from './appender';
import ListViewBlock from './block';
import { useListViewContext } from './context';
import { isClientIdSelected } from './utils';
import { store as blockEditorStore } from '../../store';
import useBlockDisplayInformation from '../use-block-display-information';
/**
* Given a block, returns the total number of blocks in that subtree. This is used to help determine
* the list position of a block.
*
* When a block is collapsed, we do not count their children as part of that total. In the current drag
* implementation dragged blocks and their children are not counted.
*
* @param {Object} block block tree
* @param {Object} expandedState state that notes which branches are collapsed
* @param {Array} draggedClientIds a list of dragged client ids
* @param {boolean} isExpandedByDefault flag to determine the default fallback expanded state.
* @return {number} block count
*/
function countBlocks(
block,
expandedState,
draggedClientIds,
isExpandedByDefault
) {
const isDragged = draggedClientIds?.includes( block.clientId );
if ( isDragged ) {
return 0;
}
const isExpanded = expandedState[ block.clientId ] ?? isExpandedByDefault;
if ( isExpanded ) {
return (
1 +
block.innerBlocks.reduce(
countReducer(
expandedState,
draggedClientIds,
isExpandedByDefault
),
0
)
);
}
return 1;
}
const countReducer =
( expandedState, draggedClientIds, isExpandedByDefault ) =>
( count, block ) => {
const isDragged = draggedClientIds?.includes( block.clientId );
if ( isDragged ) {
return count;
}
const isExpanded =
expandedState[ block.clientId ] ?? isExpandedByDefault;
if ( isExpanded && block.innerBlocks.length > 0 ) {
return (
count +
countBlocks(
block,
expandedState,
draggedClientIds,
isExpandedByDefault
)
);
}
return count + 1;
};
const noop = () => {};
function ListViewBranch( props ) {
const {
blocks,
selectBlock = noop,
showBlockMovers,
selectedClientIds,
level = 1,
path = '',
isBranchSelected = false,
listPosition = 0,
fixedListWindow,
isExpanded,
parentId,
shouldShowInnerBlocks = true,
isSyncedBranch = false,
showAppender: showAppenderProp = true,
} = props;
const parentBlockInformation = useBlockDisplayInformation( parentId );
const syncedBranch = isSyncedBranch || !! parentBlockInformation?.isSynced;
const canParentExpand = useSelect(
( select ) => {
if ( ! parentId ) {
return true;
}
return select( blockEditorStore ).canEditBlock( parentId );
},
[ parentId ]
);
const { expandedState, draggedClientIds } = useListViewContext();
if ( ! canParentExpand ) {
return null;
}
// Only show the appender at the first level.
const showAppender = showAppenderProp && level === 1;
const filteredBlocks = blocks.filter( Boolean );
const blockCount = filteredBlocks.length;
// The appender means an extra row in List View, so add 1 to the row count.
const rowCount = showAppender ? blockCount + 1 : blockCount;
let nextPosition = listPosition;
return (
<>
{ filteredBlocks.map( ( block, index ) => {
const { clientId, innerBlocks } = block;
if ( index > 0 ) {
nextPosition += countBlocks(
filteredBlocks[ index - 1 ],
expandedState,
draggedClientIds,
isExpanded
);
}
const { itemInView } = fixedListWindow;
const blockInView = itemInView( nextPosition );
const position = index + 1;
const updatedPath =
path.length > 0
? `${ path }_${ position }`
: `${ position }`;
const hasNestedBlocks = !! innerBlocks?.length;
const shouldExpand =
hasNestedBlocks && shouldShowInnerBlocks
? expandedState[ clientId ] ?? isExpanded
: undefined;
const isDragged = !! draggedClientIds?.includes( clientId );
// Make updates to the selected or dragged blocks synchronous,
// but asynchronous for any other block.
const isSelected = isClientIdSelected(
clientId,
selectedClientIds
);
const isSelectedBranch =
isBranchSelected || ( isSelected && hasNestedBlocks );
const showBlock = isDragged || blockInView || isSelected;
return (
<AsyncModeProvider key={ clientId } value={ ! isSelected }>
{ showBlock && (
<ListViewBlock
block={ block }
selectBlock={ selectBlock }
isSelected={ isSelected }
isBranchSelected={ isSelectedBranch }
isDragged={ isDragged }
level={ level }
position={ position }
rowCount={ rowCount }
siblingBlockCount={ blockCount }
showBlockMovers={ showBlockMovers }
path={ updatedPath }
isExpanded={ shouldExpand }
listPosition={ nextPosition }
selectedClientIds={ selectedClientIds }
isSyncedBranch={ syncedBranch }
/>
) }
{ ! showBlock && (
<tr>
<td className="block-editor-list-view-placeholder" />
</tr>
) }
{ hasNestedBlocks && shouldExpand && ! isDragged && (
<ListViewBranch
parentId={ clientId }
blocks={ innerBlocks }
selectBlock={ selectBlock }
showBlockMovers={ showBlockMovers }
level={ level + 1 }
path={ updatedPath }
listPosition={ nextPosition + 1 }
fixedListWindow={ fixedListWindow }
isBranchSelected={ isSelectedBranch }
selectedClientIds={ selectedClientIds }
isExpanded={ isExpanded }
isSyncedBranch={ syncedBranch }
/>
) }
</AsyncModeProvider>
);
} ) }
{ showAppender && (
<TreeGridRow
level={ level }
setSize={ rowCount }
positionInSet={ rowCount }
isExpanded={ true }
>
<TreeGridCell>
{ ( treeGridCellProps ) => (
<Appender
clientId={ parentId }
nestingLevel={ level }
blockCount={ blockCount }
{ ...treeGridCellProps }
/>
) }
</TreeGridCell>
</TreeGridRow>
) }
</>
);
}
export default memo( ListViewBranch );