diff --git a/packages/block-editor/src/components/block-list/use-in-between-inserter.js b/packages/block-editor/src/components/block-list/use-in-between-inserter.js index 0dfdab359d7a77..618811ec91ba64 100644 --- a/packages/block-editor/src/components/block-list/use-in-between-inserter.js +++ b/packages/block-editor/src/components/block-list/use-in-between-inserter.js @@ -14,12 +14,17 @@ import { InsertionPointOpenRef } from '../block-tools/insertion-point'; export function useInBetweenInserter() { const openRef = useContext( InsertionPointOpenRef ); + const hasReducedUI = useSelect( + ( select ) => select( blockEditorStore ).getSettings().hasReducedUI, + [] + ); const { getBlockListSettings, getBlockRootClientId, getBlockIndex, isBlockInsertionPointVisible, isMultiSelecting, + getSelectedBlockClientIds, } = useSelect( blockEditorStore ); const { showInsertionPoint, hideInsertionPoint } = useDispatch( blockEditorStore @@ -27,6 +32,10 @@ export function useInBetweenInserter() { return useRefEffect( ( node ) => { + if ( hasReducedUI ) { + return; + } + function onMouseMove( event ) { if ( openRef.current ) { return; @@ -98,6 +107,12 @@ export function useInBetweenInserter() { return; } + // Don't show the inserter when hovering above (conflicts with + // block toolbar) or inside selected block(s). + if ( getSelectedBlockClientIds().includes( clientId ) ) { + return; + } + const elementRect = element.getBoundingClientRect(); if ( @@ -145,6 +160,7 @@ export function useInBetweenInserter() { isMultiSelecting, showInsertionPoint, hideInsertionPoint, + getSelectedBlockClientIds, ] ); } diff --git a/packages/block-editor/src/components/block-tools/block-popover.js b/packages/block-editor/src/components/block-tools/block-popover.js index dc803f7670aba6..8371cfff9bbba8 100644 --- a/packages/block-editor/src/components/block-tools/block-popover.js +++ b/packages/block-editor/src/components/block-tools/block-popover.js @@ -64,6 +64,24 @@ function BlockPopover( { hasFixedToolbar, lastClientId, } = useSelect( selector, [] ); + const isInsertionPointVisible = useSelect( + ( select ) => { + const { + isBlockInsertionPointVisible, + getBlockInsertionPoint, + getBlockOrder, + } = select( blockEditorStore ); + + if ( ! isBlockInsertionPointVisible() ) { + return false; + } + + const insertionPoint = getBlockInsertionPoint(); + const order = getBlockOrder( insertionPoint.rootClientId ); + return order[ insertionPoint.index ] === clientId; + }, + [ clientId ] + ); const isLargeViewport = useViewportMatch( 'medium' ); const [ isToolbarForced, setIsToolbarForced ] = useState( false ); const [ isInserterShown, setIsInserterShown ] = useState( false ); @@ -184,7 +202,9 @@ function BlockPopover( { position={ popoverPosition } focusOnMount={ false } anchorRef={ anchorRef } - className="block-editor-block-list__block-popover" + className={ classnames( 'block-editor-block-list__block-popover', { + 'is-insertion-point-visible': isInsertionPointVisible, + } ) } __unstableStickyBoundaryElement={ stickyBoundaryElement } // Render in the old slot if needed for backward compatibility, // otherwise render in place (not in the the default popover slot). diff --git a/packages/block-editor/src/components/block-tools/insertion-point.js b/packages/block-editor/src/components/block-tools/insertion-point.js index 134430c301a61e..b836beba7d3ae0 100644 --- a/packages/block-editor/src/components/block-tools/insertion-point.js +++ b/packages/block-editor/src/components/block-tools/insertion-point.js @@ -36,7 +36,6 @@ function InsertionPointPopover( { const ref = useRef(); const { orientation, - isHidden, previousClientId, nextClientId, rootClientId, @@ -45,47 +44,36 @@ function InsertionPointPopover( { const { getBlockOrder, getBlockListSettings, - getMultiSelectedBlockClientIds, - getSelectedBlockClientId, - hasMultiSelection, - getSettings, getBlockInsertionPoint, + isBlockBeingDragged, + getPreviousBlockClientId, + getNextBlockClientId, } = select( blockEditorStore ); const insertionPoint = getBlockInsertionPoint(); const order = getBlockOrder( insertionPoint.rootClientId ); - const targetClientId = order[ insertionPoint.index - 1 ]; - const targetRootClientId = insertionPoint.rootClientId; - const blockOrder = getBlockOrder( targetRootClientId ); - if ( ! blockOrder.length ) { + + if ( ! order.length ) { return {}; } - const previous = targetClientId - ? targetClientId - : blockOrder[ blockOrder.length - 1 ]; - const isLast = previous === blockOrder[ blockOrder.length - 1 ]; - const next = isLast - ? null - : blockOrder[ blockOrder.indexOf( previous ) + 1 ]; - const { hasReducedUI } = getSettings(); - const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds(); - const selectedBlockClientId = getSelectedBlockClientId(); - const blockOrientation = - getBlockListSettings( targetRootClientId )?.orientation || - 'vertical'; + + let _previousClientId = order[ insertionPoint.index - 1 ]; + let _nextClientId = order[ insertionPoint.index ]; + + while ( isBlockBeingDragged( _previousClientId ) ) { + _previousClientId = getPreviousBlockClientId( _previousClientId ); + } + + while ( isBlockBeingDragged( _nextClientId ) ) { + _nextClientId = getNextBlockClientId( _nextClientId ); + } return { - previousClientId: previous, - nextClientId: next, - isHidden: - hasReducedUI || - ( hasMultiSelection() - ? next && multiSelectedBlockClientIds.includes( next ) - : next && - blockOrientation === 'vertical' && - next === selectedBlockClientId ), - orientation: blockOrientation, - clientId: targetClientId, - rootClientId: targetRootClientId, + previousClientId: _previousClientId, + nextClientId: _nextClientId, + orientation: + getBlockListSettings( insertionPoint.rootClientId ) + ?.orientation || 'vertical', + rootClientId: insertionPoint.rootClientId, isInserterShown: insertionPoint?.__unstableWithInserter, }; }, [] ); @@ -193,14 +181,7 @@ function InsertionPointPopover( { // Only show the inserter when there's a `nextElement` (a block after the // insertion point). At the end of the block list the trailing appender // should serve the purpose of inserting blocks. - const showInsertionPointInserter = - ! isHidden && nextElement && isInserterShown; - - // Show the indicator if the insertion point inserter is visible, or if - // the `showInsertionPoint` state is `true`. The latter is generally true - // when hovering blocks for insertion in the block library. - const showInsertionPointIndicator = - showInsertionPointInserter || ! isHidden; + const showInsertionPointInserter = nextElement && isInserterShown; /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ // While ideally it would be enough to capture the @@ -231,9 +212,7 @@ function InsertionPointPopover( { } ) } style={ style } > - { showInsertionPointIndicator && ( -
- ) } +
{ showInsertionPointInserter && (
{ - const { isMultiSelecting, isBlockInsertionPointVisible } = select( - blockEditorStore - ); - - return isBlockInsertionPointVisible() && ! isMultiSelecting(); + return select( blockEditorStore ).isBlockInsertionPointVisible(); }, [] ); return ( diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss index e511a41fabe92c..072b085f387bc1 100644 --- a/packages/block-editor/src/components/block-tools/style.scss +++ b/packages/block-editor/src/components/block-tools/style.scss @@ -321,6 +321,11 @@ } } + // Hide the block toolbar if the insertion point is shown. + &.is-insertion-point-visible { + visibility: hidden; + } + .is-dragging-components-draggable & { opacity: 0; // Use a minimal duration to delay hiding the element, see hide-during-dragging animation for more details. diff --git a/packages/block-editor/src/components/use-block-drop-zone/index.js b/packages/block-editor/src/components/use-block-drop-zone/index.js index 391f78ac97aa3c..019f80af6d59da 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/index.js @@ -59,16 +59,7 @@ export function getNearestBlockIndex( elements, position, orientation ) { // If the user is dropping to the trailing edge of the block // add 1 to the index to represent dragging after. const isTrailingEdge = edge === 'bottom' || edge === 'right'; - let offset = isTrailingEdge ? 1 : 0; - - // If the target is the dragged block itself and another 1 to - // index as the dragged block is set to `display: none` and - // should be skipped in the calculation. - const isTargetDraggedBlock = - isTrailingEdge && - elements[ index + 1 ] && - elements[ index + 1 ].classList.contains( 'is-dragging' ); - offset += isTargetDraggedBlock ? 1 : 0; + const offset = isTrailingEdge ? 1 : 0; // Update the currently known best candidate. candidateDistance = distance; @@ -144,6 +135,11 @@ export default function useBlockDropZone( { // https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget throttled( event, event.currentTarget ); }, + onDragLeave() { + throttled.cancel(); + hideInsertionPoint(); + setTargetBlockIndex( null ); + }, onDragEnd() { throttled.cancel(); hideInsertionPoint(); diff --git a/packages/block-editor/src/components/use-block-drop-zone/test/index.js b/packages/block-editor/src/components/use-block-drop-zone/test/index.js index c0aac7b5827d92..42a9476be0cd41 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/test/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/test/index.js @@ -209,27 +209,6 @@ describe( 'getNearestBlockIndex', () => { expect( result ).toBe( 4 ); } ); - - it( 'skips the block being dragged by checking for the `is-dragging` classname', () => { - const position = { x: 0, y: 450 }; - - const verticalElementsWithDraggedBlock = [ - ...verticalElements.slice( 0, 2 ), - { - ...verticalElements[ 2 ], - classList: createMockClassList( 'wp-block is-dragging' ), - }, - ...verticalElements.slice( 3, 4 ), - ]; - - const result = getNearestBlockIndex( - verticalElementsWithDraggedBlock, - position, - orientation - ); - - expect( result ).toBe( 3 ); - } ); } ); describe( 'Horizontal block lists', () => { @@ -342,26 +321,5 @@ describe( 'getNearestBlockIndex', () => { expect( result ).toBe( 4 ); } ); - - it( 'skips the block being dragged by checking for the `is-dragging` classname', () => { - const position = { x: 450, y: 0 }; - - const horizontalElementsWithDraggedBlock = [ - ...horizontalElements.slice( 0, 2 ), - { - ...horizontalElements[ 2 ], - classList: createMockClassList( 'wp-block is-dragging' ), - }, - ...horizontalElements.slice( 3, 4 ), - ]; - - const result = getNearestBlockIndex( - horizontalElementsWithDraggedBlock, - position, - orientation - ); - - expect( result ).toBe( 3 ); - } ); } ); } ); diff --git a/packages/compose/src/hooks/use-drop-zone/index.js b/packages/compose/src/hooks/use-drop-zone/index.js index d2052d1e8c6efe..c2a6a4224f55fb 100644 --- a/packages/compose/src/hooks/use-drop-zone/index.js +++ b/packages/compose/src/hooks/use-drop-zone/index.js @@ -70,6 +70,30 @@ export default function useDropZone( { const { ownerDocument } = element; + /** + * Checks if an element is in the drop zone. + * + * @param {HTMLElement|null} elementToCheck + * + * @return {boolean} True if in drop zone, false if not. + */ + function isElementInZone( elementToCheck ) { + if ( + ! elementToCheck || + ! element.contains( elementToCheck ) + ) { + return false; + } + + do { + if ( elementToCheck.dataset.isDropZone ) { + return elementToCheck === element; + } + } while ( ( elementToCheck = elementToCheck.parentElement ) ); + + return false; + } + function maybeDragStart( /** @type {DragEvent} */ event ) { if ( isDragging ) { return; @@ -82,6 +106,13 @@ export default function useDropZone( { maybeDragStart ); + // Note that `dragend` doesn't fire consistently for file and + // HTML drag events where the drag origin is outside the browser + // window. In Firefox it may also not fire if the originating + // node is removed. + ownerDocument.addEventListener( 'dragend', maybeDragEnd ); + ownerDocument.addEventListener( 'mousemove', maybeDragEnd ); + if ( onDragStartRef.current ) { onDragStartRef.current( event ); } @@ -125,8 +156,8 @@ export default function useDropZone( { // (element that has been entered) should be outside the drop // zone. if ( - element.contains( - /** @type {Node} */ ( event.relatedTarget ) + isElementInZone( + /** @type {HTMLElement|null} */ ( event.relatedTarget ) ) ) { return; @@ -168,33 +199,31 @@ export default function useDropZone( { isDragging = false; ownerDocument.addEventListener( 'dragenter', maybeDragStart ); + ownerDocument.removeEventListener( 'dragend', maybeDragEnd ); + ownerDocument.removeEventListener( 'mousemove', maybeDragEnd ); if ( onDragEndRef.current ) { onDragEndRef.current( event ); } } + element.dataset.isDropZone = 'true'; element.addEventListener( 'drop', onDrop ); element.addEventListener( 'dragenter', onDragEnter ); element.addEventListener( 'dragover', onDragOver ); element.addEventListener( 'dragleave', onDragLeave ); - // Note that `dragend` doesn't fire consistently for file and HTML - // drag events where the drag origin is outside the browser window. - // In Firefox it may also not fire if the originating node is - // removed. - ownerDocument.addEventListener( 'dragend', maybeDragEnd ); - ownerDocument.addEventListener( 'mouseup', maybeDragEnd ); // The `dragstart` event doesn't fire if the drag started outside // the document. ownerDocument.addEventListener( 'dragenter', maybeDragStart ); return () => { + delete element.dataset.isDropZone; element.removeEventListener( 'drop', onDrop ); element.removeEventListener( 'dragenter', onDragEnter ); element.removeEventListener( 'dragover', onDragOver ); element.removeEventListener( 'dragleave', onDragLeave ); ownerDocument.removeEventListener( 'dragend', maybeDragEnd ); - ownerDocument.removeEventListener( 'mouseup', maybeDragEnd ); + ownerDocument.removeEventListener( 'mousemove', maybeDragEnd ); ownerDocument.addEventListener( 'dragenter', maybeDragStart ); }; },