diff --git a/packages/block-editor/src/components/block-list/block-list-item-cell.native.js b/packages/block-editor/src/components/block-list/block-list-item-cell.native.js
index e32d793af46d6..80aa2589eb64f 100644
--- a/packages/block-editor/src/components/block-list/block-list-item-cell.native.js
+++ b/packages/block-editor/src/components/block-list/block-list-item-cell.native.js
@@ -7,14 +7,23 @@ import { View } from 'react-native';
* WordPress dependencies
*/
import { useEffect, useCallback } from '@wordpress/element';
+import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import { useBlockListContext } from './block-list-context';
+import { store as blockEditorStore } from '../../store';
-function BlockListItemCell( { children, clientId, rootClientId, onLayout } ) {
+function BlockListItemCell( { children, item: clientId, onLayout } ) {
const { blocksLayouts, updateBlocksLayouts } = useBlockListContext();
+ const { rootClientId } = useSelect(
+ ( select ) => {
+ const { getBlockRootClientId } = select( blockEditorStore );
+ return { rootClientId: getBlockRootClientId( clientId ) };
+ },
+ [ clientId ]
+ );
useEffect( () => {
return () => {
diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js
index 0a1458db02bb9..48f839b156d45 100644
--- a/packages/block-editor/src/components/block-list/index.native.js
+++ b/packages/block-editor/src/components/block-list/index.native.js
@@ -6,9 +6,8 @@ import { View, Platform, TouchableWithoutFeedback } from 'react-native';
/**
* WordPress dependencies
*/
-import { Component } from '@wordpress/element';
-import { withDispatch, withSelect } from '@wordpress/data';
-import { compose, withPreferredColorScheme } from '@wordpress/compose';
+import { useRef, useState } from '@wordpress/element';
+import { useSelect, useDispatch } from '@wordpress/data';
import { createBlock } from '@wordpress/blocks';
import {
KeyboardAwareFlatList,
@@ -57,83 +56,117 @@ const getStyles = (
return computedStyles;
};
-export class BlockList extends Component {
- constructor() {
- super( ...arguments );
- this.extraData = {
- parentWidth: this.props.parentWidth,
- renderFooterAppender: this.props.renderFooterAppender,
- renderAppender: this.props.renderAppender,
- onDeleteBlock: this.props.onDeleteBlock,
- contentStyle: this.props.contentStyle,
- };
- this.renderItem = this.renderItem.bind( this );
- this.renderBlockListFooter = this.renderBlockListFooter.bind( this );
- this.scrollViewInnerRef = this.scrollViewInnerRef.bind( this );
- this.addBlockToEndOfPost = this.addBlockToEndOfPost.bind( this );
- this.shouldFlatListPreventAutomaticScroll =
- this.shouldFlatListPreventAutomaticScroll.bind( this );
- this.shouldShowInnerBlockAppender =
- this.shouldShowInnerBlockAppender.bind( this );
- this.renderEmptyList = this.renderEmptyList.bind( this );
- this.getExtraData = this.getExtraData.bind( this );
- this.getCellRendererComponent =
- this.getCellRendererComponent.bind( this );
-
- this.onLayout = this.onLayout.bind( this );
-
- this.state = {
- blockWidth: this.props.blockWidth || 0,
- };
- }
+export default function BlockList( {
+ blockWidth: initialBlockWidth,
+ contentResizeMode,
+ contentStyle,
+ filterInnerBlocks,
+ gridProperties,
+ header,
+ horizontal,
+ horizontalAlignment,
+ marginHorizontal = styles.defaultBlock.marginLeft,
+ marginVertical = styles.defaultBlock.marginTop,
+ onAddBlock,
+ onDeleteBlock,
+ orientation,
+ parentWidth,
+ renderAppender,
+ renderFooterAppender,
+ rootClientId,
+ title,
+ withFooter = true,
+} ) {
+ const {
+ blockClientIds,
+ blockCount,
+ blockInsertionPointIsVisible,
+ isReadOnly,
+ isRootList,
+ isFloatingToolbarVisible,
+ isStackedHorizontally,
+ maxWidth,
+ isRTL,
+ } = useSelect(
+ ( select ) => {
+ const {
+ getBlockCount,
+ getBlockHierarchyRootClientId,
+ getBlockOrder,
+ getSelectedBlockClientId,
+ isBlockInsertionPointVisible,
+ getSettings,
+ } = select( blockEditorStore );
- addBlockToEndOfPost( newBlock ) {
- this.props.insertBlock( newBlock, this.props.blockCount );
- }
+ const selectedBlockClientId = getSelectedBlockClientId();
+ const rootBlockId = getBlockHierarchyRootClientId(
+ selectedBlockClientId
+ );
- scrollViewInnerRef( ref ) {
- this.scrollViewRef = ref;
- }
+ let blockOrder = getBlockOrder( rootClientId );
+ // Display only block which fulfill the condition in passed `filterInnerBlocks` function.
+ if ( filterInnerBlocks ) {
+ blockOrder = filterInnerBlocks( blockOrder );
+ }
- shouldFlatListPreventAutomaticScroll() {
- return this.props.isBlockInsertionPointVisible;
- }
+ const {
+ isRTL: isRTLSetting,
+ maxWidth: maxWidthSetting,
+ readOnly,
+ } = getSettings();
- shouldShowInnerBlockAppender() {
- const { blockClientIds, renderAppender } = this.props;
- return renderAppender && blockClientIds.length > 0;
- }
+ return {
+ blockClientIds: blockOrder,
+ blockCount: getBlockCount(),
+ blockInsertionPointIsVisible:
+ Platform.OS === 'ios' && isBlockInsertionPointVisible(),
+ isReadOnly: readOnly,
+ isRootList: rootClientId === undefined,
+ isFloatingToolbarVisible:
+ !! selectedBlockClientId && !! getBlockCount( rootBlockId ),
+ isStackedHorizontally: orientation === 'horizontal',
+ maxWidth: maxWidthSetting,
+ isRTL: isRTLSetting,
+ };
+ },
+ [ filterInnerBlocks, orientation, rootClientId ]
+ );
- renderEmptyList() {
- return (
-
- );
- }
+ const { insertBlock, clearSelectedBlock } = useDispatch( blockEditorStore );
- getExtraData() {
- const {
- parentWidth,
- renderFooterAppender,
- onDeleteBlock,
- contentStyle,
- renderAppender,
- gridProperties,
- } = this.props;
- const { blockWidth } = this.state;
+ const extraData = useRef( {
+ parentWidth,
+ renderFooterAppender,
+ renderAppender,
+ onDeleteBlock,
+ contentStyle,
+ } );
+
+ const [ blockWidth, setBlockWidth ] = useState( initialBlockWidth || 0 );
+
+ const addBlockToEndOfPost = ( newBlock ) => {
+ insertBlock( newBlock, blockCount );
+ };
+
+ const scrollViewRef = useRef( null );
+
+ const shouldFlatListPreventAutomaticScroll = () =>
+ blockInsertionPointIsVisible;
+
+ const shouldShowInnerBlockAppender = () =>
+ renderAppender && blockClientIds.length > 0;
+
+ const getExtraData = () => {
if (
- this.extraData.parentWidth !== parentWidth ||
- this.extraData.renderFooterAppender !== renderFooterAppender ||
- this.extraData.onDeleteBlock !== onDeleteBlock ||
- this.extraData.contentStyle !== contentStyle ||
- this.extraData.renderAppender !== renderAppender ||
- this.extraData.blockWidth !== blockWidth ||
- this.extraData.gridProperties !== gridProperties
+ extraData.current.parentWidth !== parentWidth ||
+ extraData.current.renderFooterAppender !== renderFooterAppender ||
+ extraData.current.onDeleteBlock !== onDeleteBlock ||
+ extraData.current.contentStyle !== contentStyle ||
+ extraData.current.renderAppender !== renderAppender ||
+ extraData.current.blockWidth !== blockWidth ||
+ extraData.current.gridProperties !== gridProperties
) {
- this.extraData = {
+ extraData.current = {
parentWidth,
renderFooterAppender,
onDeleteBlock,
@@ -143,81 +176,21 @@ export class BlockList extends Component {
gridProperties,
};
}
- return this.extraData;
- }
+ return extraData.current;
+ };
- getCellRendererComponent( { children, item, onLayout } ) {
- const { rootClientId } = this.props;
- return (
-
- );
- }
-
- onLayout( { nativeEvent } ) {
+ const onLayout = ( { nativeEvent } ) => {
const { layout } = nativeEvent;
- const { blockWidth } = this.state;
- const { isRootList, maxWidth } = this.props;
const layoutWidth = Math.floor( layout.width );
if ( isRootList && blockWidth !== layoutWidth ) {
- this.setState( {
- blockWidth: Math.min( layoutWidth, maxWidth ),
- } );
+ setBlockWidth( Math.min( layoutWidth, maxWidth ) );
} else if ( ! isRootList && ! blockWidth ) {
- this.setState( { blockWidth: Math.min( layoutWidth, maxWidth ) } );
+ setBlockWidth( Math.min( layoutWidth, maxWidth ) );
}
- }
-
- render() {
- const { isRootList, isRTL } = this.props;
- // Use of Context to propagate the main scroll ref to its children e.g InnerBlocks.
- const blockList = isRootList ? (
-
-
- { ( { onScroll } ) => this.renderList( { onScroll } ) }
-
-
- ) : (
-
- { ( { scrollRef } ) =>
- this.renderList( {
- parentScrollRef: scrollRef,
- } )
- }
-
- );
-
- return blockList;
- }
+ };
- renderList( extraProps = {} ) {
- const {
- clearSelectedBlock,
- blockClientIds,
- rootClientId,
- title,
- header,
- isReadOnly,
- isRootList,
- horizontal,
- marginVertical = styles.defaultBlock.marginTop,
- marginHorizontal = styles.defaultBlock.marginLeft,
- isFloatingToolbarVisible,
- isStackedHorizontally,
- horizontalAlignment,
- contentResizeMode,
- blockWidth,
- } = this.props;
+ const renderList = ( extraProps = {} ) => {
const { parentScrollRef, onScroll } = extraProps;
const { blockToolbar, headerToolbar, floatingToolbar } = styles;
@@ -246,7 +219,7 @@ export class BlockList extends Component {
{
- this.scrollViewInnerRef( parentScrollRef || ref );
+ scrollViewRef.current = parentScrollRef || ref;
} }
extraScrollHeight={ extraScrollHeight }
keyboardShouldPersistTaps="always"
scrollViewStyle={ scrollViewStyle }
- extraData={ this.getExtraData() }
+ extraData={ getExtraData() }
scrollEnabled={ isRootList }
contentContainerStyle={ [
horizontal && styles.horizontalContentContainer,
@@ -279,18 +252,34 @@ export class BlockList extends Component {
listKey={
rootClientId ? `list-${ rootClientId }` : 'list-root'
}
- renderItem={ this.renderItem }
- CellRendererComponent={ this.getCellRendererComponent }
+ renderItem={ renderItem }
+ CellRendererComponent={ BlockListItemCell }
shouldPreventAutomaticScroll={
- this.shouldFlatListPreventAutomaticScroll
+ shouldFlatListPreventAutomaticScroll
}
title={ title }
ListHeaderComponent={ header }
- ListEmptyComponent={ ! isReadOnly && this.renderEmptyList }
- ListFooterComponent={ this.renderBlockListFooter }
+ ListEmptyComponent={
+ ! isReadOnly && (
+
+ )
+ }
+ ListFooterComponent={
+
+ }
onScroll={ onScroll }
/>
- { this.shouldShowInnerBlockAppender() && (
+ { shouldShowInnerBlockAppender() && (
) }
);
- }
-
- renderItem( { item: clientId, index } ) {
- const {
- contentResizeMode,
- contentStyle,
- onAddBlock,
- onDeleteBlock,
- rootClientId,
- isStackedHorizontally,
- blockClientIds,
- parentWidth,
- marginVertical = styles.defaultBlock.marginTop,
- marginHorizontal = styles.defaultBlock.marginLeft,
- gridProperties,
- } = this.props;
- const { blockWidth } = this.state;
+ };
+ const renderItem = ( { item: clientId, index } ) => {
// Extracting the grid item properties here to avoid
// re-renders in the blockListItem component.
const isGridItem = !! gridProperties;
@@ -346,142 +320,74 @@ export class BlockList extends Component {
marginVertical={ marginVertical }
marginHorizontal={ marginHorizontal }
onDeleteBlock={ onDeleteBlock }
- shouldShowInnerBlockAppender={
- this.shouldShowInnerBlockAppender
- }
+ shouldShowInnerBlockAppender={ shouldShowInnerBlockAppender }
blockWidth={ blockWidth }
isGridItem={ isGridItem }
{ ...gridItemProps }
/>
);
- }
-
- renderBlockListFooter() {
- const paragraphBlock = createBlock( 'core/paragraph' );
- const {
- isReadOnly,
- withFooter = true,
- renderFooterAppender,
- } = this.props;
-
- if ( ! isReadOnly && withFooter ) {
- return (
- <>
- {
- this.addBlockToEndOfPost( paragraphBlock );
- } }
- >
-
-
- >
- );
- } else if ( renderFooterAppender ) {
- return renderFooterAppender();
- }
- return null;
- }
-}
-
-export default compose( [
- withSelect(
- ( select, { rootClientId, orientation, filterInnerBlocks } ) => {
- const {
- getBlockCount,
- getBlockHierarchyRootClientId,
- getBlockOrder,
- getSelectedBlockClientId,
- isBlockInsertionPointVisible,
- getSettings,
- } = select( blockEditorStore );
-
- const isStackedHorizontally = orientation === 'horizontal';
-
- const selectedBlockClientId = getSelectedBlockClientId();
-
- let blockClientIds = getBlockOrder( rootClientId );
- // Display only block which fulfill the condition in passed `filterInnerBlocks` function.
- if ( filterInnerBlocks ) {
- blockClientIds = filterInnerBlocks( blockClientIds );
+ };
+
+ // Use of Context to propagate the main scroll ref to its children e.g InnerBlocks.
+ const blockList = isRootList ? (
+
+
+ { ( { onScroll } ) => renderList( { onScroll } ) }
+
+
+ ) : (
+
+ { ( { scrollRef } ) =>
+ renderList( {
+ parentScrollRef: scrollRef,
+ } )
}
+
+ );
- const { maxWidth } = getSettings();
- const isReadOnly = getSettings().readOnly;
-
- const blockCount = getBlockCount();
- const rootBlockId = getBlockHierarchyRootClientId(
- selectedBlockClientId
- );
-
- const isFloatingToolbarVisible =
- !! selectedBlockClientId && !! getBlockCount( rootBlockId );
- const isRTL = getSettings().isRTL;
-
- return {
- blockClientIds,
- blockCount,
- isBlockInsertionPointVisible:
- Platform.OS === 'ios' && isBlockInsertionPointVisible(),
- isReadOnly,
- isRootList: rootClientId === undefined,
- rootClientId,
- isFloatingToolbarVisible,
- isStackedHorizontally,
- maxWidth,
- isRTL,
- };
- }
- ),
- withDispatch( ( dispatch ) => {
- const { insertBlock, replaceBlock, clearSelectedBlock } =
- dispatch( blockEditorStore );
-
- return {
- clearSelectedBlock,
- insertBlock,
- replaceBlock,
- };
- } ),
- withPreferredColorScheme,
-] )( BlockList );
-
-class EmptyListComponent extends Component {
- render() {
- const {
- shouldShowInsertionPoint,
- rootClientId,
- renderAppender,
- renderFooterAppender,
- } = this.props;
-
- if ( renderFooterAppender || renderAppender === false ) {
- return null;
- }
+ return blockList;
+}
+function Footer( {
+ addBlockToEndOfPost,
+ isReadOnly,
+ renderFooterAppender,
+ withFooter,
+} ) {
+ if ( ! isReadOnly && withFooter ) {
return (
-
-
+ {
+ const paragraphBlock = createBlock( 'core/paragraph' );
+ addBlockToEndOfPost( paragraphBlock );
+ } }
>
-
-
-
+
+
+ >
);
+ } else if ( renderFooterAppender ) {
+ return renderFooterAppender();
}
+
+ return null;
}
-const EmptyListComponentCompose = compose( [
- withSelect( ( select, { rootClientId, orientation } ) => {
+function EmptyList( {
+ orientation,
+ renderAppender,
+ renderFooterAppender,
+ rootClientId,
+} ) {
+ const { shouldShowInsertionPoint } = useSelect( ( select ) => {
const {
getBlockOrder,
getBlockInsertionPoint,
@@ -492,17 +398,36 @@ const EmptyListComponentCompose = compose( [
const blockClientIds = getBlockOrder( rootClientId );
const insertionPoint = getBlockInsertionPoint();
const blockInsertionPointIsVisible = isBlockInsertionPointVisible();
- const shouldShowInsertionPoint =
- ! isStackedHorizontally &&
- blockInsertionPointIsVisible &&
- insertionPoint.rootClientId === rootClientId &&
- // If list is empty, show the insertion point (via the default appender)
- ( blockClientIds.length === 0 ||
- // Or if the insertion point is right before the denoted block.
- ! blockClientIds[ insertionPoint.index ] );
return {
- shouldShowInsertionPoint,
+ shouldShowInsertionPoint:
+ ! isStackedHorizontally &&
+ blockInsertionPointIsVisible &&
+ insertionPoint.rootClientId === rootClientId &&
+ // If list is empty, show the insertion point (via the default appender)
+ ( blockClientIds.length === 0 ||
+ // Or if the insertion point is right before the denoted block.
+ ! blockClientIds[ insertionPoint.index ] ),
};
- } ),
-] )( EmptyListComponent );
+ } );
+
+ if ( renderFooterAppender || renderAppender === false ) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/packages/block-editor/src/components/block-list/test/index.native.js b/packages/block-editor/src/components/block-list/test/index.native.js
index 2f34568602ffb..dee27b963d162 100644
--- a/packages/block-editor/src/components/block-list/test/index.native.js
+++ b/packages/block-editor/src/components/block-list/test/index.native.js
@@ -12,7 +12,7 @@ import {
within,
} from 'test/helpers';
-setupCoreBlocks( [ 'core/paragraph', 'core/group' ] );
+setupCoreBlocks();
describe( 'BlockList', () => {
describe( 'when empty', () => {
@@ -27,22 +27,58 @@ describe( 'BlockList', () => {
} );
it( 'renders a block appender as a content placeholder', async () => {
+ // Act
+ const appender = screen.getByPlaceholderText( /Start writing/ );
+ fireEvent.press( appender );
+
// Assert
- expect(
- screen.getByPlaceholderText( /Start writing/ )
- ).toBeTruthy();
+ expect( await getBlock( screen, 'Paragraph' ) ).toBeVisible();
} );
it( 'renders an end-of-list paragraph appender', async () => {
+ // Act
+ const appender = screen.getByLabelText( 'Add paragraph block' );
+ fireEvent.press( appender );
+
// Assert
- expect(
- screen.getByLabelText( 'Add paragraph block' )
- ).toBeTruthy();
+ expect( await getBlock( screen, 'Paragraph' ) ).toBeVisible();
+ } );
+ } );
+
+ describe( 'when not empty', () => {
+ it( 'renders a footer appender', async () => {
+ // Arrange
+ await initializeEditor();
+ await addBlock( screen, 'Social Icons' );
+ const socialLinksBlock = await getBlock( screen, 'Social Icons' );
+ fireEvent.press( socialLinksBlock );
+ triggerBlockListLayout( socialLinksBlock );
+
+ // Act
+ fireEvent.press(
+ within( socialLinksBlock ).getByTestId( 'appender-button' )
+ );
+ const blockList = screen.getByTestId( 'InserterUI-Blocks' );
+ fireEvent.scroll( blockList, {
+ nativeEvent: {
+ contentOffset: { y: 0, x: 0 },
+ contentSize: { width: 100, height: 100 },
+ layoutMeasurement: { width: 100, height: 100 },
+ },
+ } );
+ fireEvent.press( await screen.findByText( 'Amazon' ) );
+
+ // Assert
+ const amazonSettings = await screen.findByTestId(
+ 'navigation-screen-LinkSettingsScreen'
+ );
+ expect( amazonSettings ).toBeVisible();
} );
} );
describe( 'for inner blocks', () => {
it( 'renders an inner block appender', async () => {
+ // Arrange
await initializeEditor();
await addBlock( screen, 'Group' );
const groupBlock = await getBlock( screen, 'Group' );
diff --git a/test/native/integration-test-helpers/wait-for-store-resolvers.js b/test/native/integration-test-helpers/wait-for-store-resolvers.js
index ce4fa664e1a2c..ebf2c9cd1a1e7 100644
--- a/test/native/integration-test-helpers/wait-for-store-resolvers.js
+++ b/test/native/integration-test-helpers/wait-for-store-resolvers.js
@@ -25,7 +25,7 @@ export async function waitForStoreResolvers( fn ) {
const result = fn();
// Advance all timers allowing store resolvers to resolve.
- act( () => jest.runAllTimers() );
+ act( () => jest.runOnlyPendingTimers() );
// The store resolvers perform several API fetches during editor
// initialization. The most straightforward approach to ensure all of them