UNPKG

@wordpress/block-editor

Version:
242 lines (219 loc) 6.26 kB
/** * External dependencies */ import classnames from 'classnames'; /** * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; import { useRef, createContext, useContext } from '@wordpress/element'; import { __unstableMotion as motion } from '@wordpress/components'; import { useReducedMotion } from '@wordpress/compose'; /** * Internal dependencies */ import Inserter from '../inserter'; import { store as blockEditorStore } from '../../store'; import BlockPopoverInbetween from '../block-popover/inbetween'; import BlockDropZonePopover from '../block-popover/drop-zone'; export const InsertionPointOpenRef = createContext(); function InbetweenInsertionPointPopover( { __unstablePopoverSlot, __unstableContentRef, } ) { const { selectBlock, hideInsertionPoint } = useDispatch( blockEditorStore ); const openRef = useContext( InsertionPointOpenRef ); const ref = useRef(); const { orientation, previousClientId, nextClientId, rootClientId, isInserterShown, isDistractionFree, isNavigationMode, } = useSelect( ( select ) => { const { getBlockOrder, getBlockListSettings, getBlockInsertionPoint, isBlockBeingDragged, getPreviousBlockClientId, getNextBlockClientId, getSettings, isNavigationMode: _isNavigationMode, } = select( blockEditorStore ); const insertionPoint = getBlockInsertionPoint(); const order = getBlockOrder( insertionPoint.rootClientId ); if ( ! order.length ) { return {}; } let _previousClientId = order[ insertionPoint.index - 1 ]; let _nextClientId = order[ insertionPoint.index ]; while ( isBlockBeingDragged( _previousClientId ) ) { _previousClientId = getPreviousBlockClientId( _previousClientId ); } while ( isBlockBeingDragged( _nextClientId ) ) { _nextClientId = getNextBlockClientId( _nextClientId ); } const settings = getSettings(); return { previousClientId: _previousClientId, nextClientId: _nextClientId, orientation: getBlockListSettings( insertionPoint.rootClientId ) ?.orientation || 'vertical', rootClientId: insertionPoint.rootClientId, isNavigationMode: _isNavigationMode(), isDistractionFree: settings.isDistractionFree, isInserterShown: insertionPoint?.__unstableWithInserter, }; }, [] ); const disableMotion = useReducedMotion(); function onClick( event ) { if ( event.target === ref.current && nextClientId ) { selectBlock( nextClientId, -1 ); } } function maybeHideInserterPoint( event ) { // Only hide the inserter if it's triggered on the wrapper, // and the inserter is not open. if ( event.target === ref.current && ! openRef.current ) { hideInsertionPoint(); } } function onFocus( event ) { // Only handle click on the wrapper specifically, and not an event // bubbled from the inserter itself. if ( event.target !== ref.current ) { openRef.current = true; } } const lineVariants = { // Initial position starts from the center and invisible. start: { opacity: 0, scale: 0, }, // The line expands to fill the container. If the inserter is visible it // is delayed so it appears orchestrated. rest: { opacity: 1, scale: 1, transition: { delay: isInserterShown ? 0.5 : 0, type: 'tween' }, }, hover: { opacity: 1, scale: 1, transition: { delay: 0.5, type: 'tween' }, }, }; const inserterVariants = { start: { scale: disableMotion ? 1 : 0, }, rest: { scale: 1, transition: { delay: 0.4, type: 'tween' }, }, }; if ( isDistractionFree && ! isNavigationMode ) { return null; } const className = classnames( 'block-editor-block-list__insertion-point', 'is-' + orientation ); return ( <BlockPopoverInbetween previousClientId={ previousClientId } nextClientId={ nextClientId } __unstablePopoverSlot={ __unstablePopoverSlot } __unstableContentRef={ __unstableContentRef } > <motion.div layout={ ! disableMotion } initial={ disableMotion ? 'rest' : 'start' } animate="rest" whileHover="hover" whileTap="pressed" exit="start" ref={ ref } tabIndex={ -1 } onClick={ onClick } onFocus={ onFocus } className={ classnames( className, { 'is-with-inserter': isInserterShown, } ) } onHoverEnd={ maybeHideInserterPoint } > <motion.div variants={ lineVariants } className="block-editor-block-list__insertion-point-indicator" data-testid="block-list-insertion-point-indicator" /> { isInserterShown && ( <motion.div variants={ inserterVariants } className={ classnames( 'block-editor-block-list__insertion-point-inserter' ) } > <Inserter position="bottom center" clientId={ nextClientId } rootClientId={ rootClientId } __experimentalIsQuick onToggle={ ( isOpen ) => { openRef.current = isOpen; } } onSelectOrClose={ () => { openRef.current = false; } } /> </motion.div> ) } </motion.div> </BlockPopoverInbetween> ); } export default function InsertionPoint( props ) { const { insertionPoint, isVisible, isBlockListEmpty } = useSelect( ( select ) => { const { getBlockInsertionPoint, isBlockInsertionPointVisible, getBlockCount, } = select( blockEditorStore ); const blockInsertionPoint = getBlockInsertionPoint(); return { insertionPoint: blockInsertionPoint, isVisible: isBlockInsertionPointVisible(), isBlockListEmpty: getBlockCount( blockInsertionPoint?.rootClientId ) === 0, }; }, [] ); if ( ! isVisible || // Don't render the insertion point if the block list is empty. // The insertion point will be represented by the appender instead. isBlockListEmpty ) { return null; } /** * Render a popover that overlays the block when the desired operation is to replace it. * Otherwise, render a popover in between blocks for the indication of inserting between them. */ return insertionPoint.operation === 'replace' ? ( <BlockDropZonePopover // Force remount to trigger the animation. key={ `${ insertionPoint.rootClientId }-${ insertionPoint.index }` } { ...props } /> ) : ( <InbetweenInsertionPointPopover { ...props } /> ); }