@wordpress/block-editor
Version:
237 lines (213 loc) • 5.97 kB
JavaScript
/**
* External dependencies
*/
import { AccessibilityInfo, TouchableHighlight, Platform } from 'react-native';
/**
* WordPress dependencies
*/
import { useEffect, useState, useCallback } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { createBlock } from '@wordpress/blocks';
import {
BottomSheet,
BottomSheetConsumer,
SearchControl,
} from '@wordpress/components';
/**
* Internal dependencies
*/
import InserterSearchResults from './search-results';
import { store as blockEditorStore } from '../../store';
import InserterTabs from './tabs';
import styles from './style.scss';
import { filterInserterItems } from './utils';
const MIN_ITEMS_FOR_SEARCH = 2;
function InserterMenu( {
onSelect,
onDismiss,
rootClientId,
clientId,
isAppender,
shouldReplaceBlock,
insertionIndex,
} ) {
const [ filterValue, setFilterValue ] = useState( '' );
const [ showTabs, setShowTabs ] = useState( true );
const [ tabIndex, setTabIndex ] = useState( 0 );
const isIOS = Platform.OS === 'ios';
const {
showInsertionPoint,
hideInsertionPoint,
clearSelectedBlock,
insertBlock,
removeBlock,
resetBlocks,
insertDefaultBlock,
} = useDispatch( blockEditorStore );
const { items, destinationRootClientId, showReusableBlocks } = useSelect(
( select ) => {
const {
getInserterItems,
getBlockRootClientId,
getBlockSelectionEnd,
} = select( blockEditorStore );
let targetRootClientId = rootClientId;
if ( ! targetRootClientId && ! clientId && ! isAppender ) {
const end = getBlockSelectionEnd();
if ( end ) {
targetRootClientId =
getBlockRootClientId( end ) || undefined;
}
}
const allItems = getInserterItems( targetRootClientId );
const reusableBlockItems = filterInserterItems( allItems, {
onlyReusable: true,
} );
return {
items: allItems,
destinationRootClientId: targetRootClientId,
showReusableBlocks: !! reusableBlockItems.length,
};
}
);
const { getBlockOrder, getBlockCount } = useSelect( blockEditorStore );
useEffect( () => {
// Show/Hide insertion point on Mount/Dismount
if ( shouldReplaceBlock ) {
const count = getBlockCount();
// Check if there is a rootClientId because that means it is a nested replaceable block
// and we don't want to clear/reset all blocks.
if ( count === 1 && ! rootClientId ) {
// Removing the last block is not possilble with `removeBlock` action.
// It always inserts a default block if the last of the blocks have been removed.
clearSelectedBlock();
resetBlocks( [] );
} else {
const blockToReplace = getBlockOrder( destinationRootClientId )[
insertionIndex
];
removeBlock( blockToReplace, false );
}
}
showInsertionPoint( destinationRootClientId, insertionIndex );
return hideInsertionPoint;
}, [] );
const onClose = useCallback( () => {
// If should replace but didn't insert any block
// re-insert default block.
if ( shouldReplaceBlock ) {
insertDefaultBlock( {}, destinationRootClientId, insertionIndex );
}
onDismiss();
}, [ shouldReplaceBlock, destinationRootClientId, insertionIndex ] );
const onInsert = useCallback(
( item ) => {
const { name, initialAttributes, innerBlocks } = item;
const newBlock = createBlock(
name,
initialAttributes,
innerBlocks
);
insertBlock(
newBlock,
insertionIndex,
destinationRootClientId,
true,
{ source: 'inserter_menu' }
);
},
[ insertBlock, destinationRootClientId, insertionIndex ]
);
const onSelectItem = useCallback(
( item ) => {
// Avoid a focus loop, see https://github.com/WordPress/gutenberg/issues/30562
if ( Platform.OS === 'ios' ) {
AccessibilityInfo.isScreenReaderEnabled().then( ( enabled ) => {
// In testing, the bug focus loop needed a longer timeout when VoiceOver was enabled.
const timeout = enabled ? 200 : 100;
// eslint-disable-next-line @wordpress/react-no-unsafe-timeout
setTimeout( () => {
onInsert( item );
}, timeout );
} );
} else {
onInsert( item );
}
onSelect( item );
},
[ onInsert, onSelect ]
);
const onChangeSearch = useCallback(
( value ) => {
setFilterValue( value );
},
[ setFilterValue ]
);
const onKeyboardShow = useCallback(
() => setShowTabs( false ),
[ setShowTabs ]
);
const onKeyboardHide = useCallback(
() => setShowTabs( true ),
[ setShowTabs ]
);
const showSearchForm = items.length > MIN_ITEMS_FOR_SEARCH;
const isFullScreen = ! isIOS && showSearchForm;
return (
<BottomSheet
isVisible
onClose={ onClose }
onKeyboardShow={ onKeyboardShow }
onKeyboardHide={ onKeyboardHide }
header={
<>
{ showSearchForm && (
<SearchControl
onChange={ onChangeSearch }
value={ filterValue }
/>
) }
{ showTabs && ! filterValue && (
<InserterTabs.Control
onChangeTab={ setTabIndex }
showReusableBlocks={ showReusableBlocks }
/>
) }
</>
}
hasNavigation
setMinHeightToMaxHeight
contentStyle={ styles[ 'inserter-menu__list' ] }
isFullScreen={ isFullScreen }
allowDragIndicator
>
<BottomSheetConsumer>
{ ( { listProps } ) => (
<TouchableHighlight
accessible={ false }
style={ styles[ 'inserter-menu__list-wrapper' ] }
>
{ ! showTabs || filterValue ? (
<InserterSearchResults
rootClientId={ rootClientId }
filterValue={ filterValue }
onSelect={ onSelectItem }
listProps={ listProps }
isFullScreen={ isFullScreen }
/>
) : (
<InserterTabs
rootClientId={ rootClientId }
listProps={ listProps }
tabIndex={ tabIndex }
onSelect={ onSelectItem }
showReusableBlocks={ showReusableBlocks }
/>
) }
</TouchableHighlight>
) }
</BottomSheetConsumer>
</BottomSheet>
);
}
export default InserterMenu;