diff --git a/packages/block-editor/src/components/block-draggable/draggable-chip.js b/packages/block-editor/src/components/block-draggable/draggable-chip.js index d7d053be179fa..cca21e7e0b785 100644 --- a/packages/block-editor/src/components/block-draggable/draggable-chip.js +++ b/packages/block-editor/src/components/block-draggable/draggable-chip.js @@ -10,7 +10,12 @@ import { dragHandle } from '@wordpress/icons'; */ import BlockIcon from '../block-icon'; -export default function BlockDraggableChip( { count, icon, isPattern } ) { +export default function BlockDraggableChip( { + count, + icon, + isPattern, + fadeWhenDisabled, +} ) { const patternLabel = isPattern && __( 'Pattern' ); return (
@@ -37,6 +42,11 @@ export default function BlockDraggableChip( { count, icon, isPattern } ) { + { fadeWhenDisabled && ( + + + + ) }
diff --git a/packages/block-editor/src/components/block-draggable/index.js b/packages/block-editor/src/components/block-draggable/index.js index 0b8f3c2d87f52..7191a0f1428a9 100644 --- a/packages/block-editor/src/components/block-draggable/index.js +++ b/packages/block-editor/src/components/block-draggable/index.js @@ -5,6 +5,7 @@ import { store as blocksStore } from '@wordpress/blocks'; import { Draggable } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { useEffect, useRef } from '@wordpress/element'; +import { throttle } from '@wordpress/compose'; /** * Internal dependencies @@ -12,6 +13,8 @@ import { useEffect, useRef } from '@wordpress/element'; import BlockDraggableChip from './draggable-chip'; import useScrollWhenDragging from './use-scroll-when-dragging'; import { store as blockEditorStore } from '../../store'; +import { __unstableUseBlockRef as useBlockRef } from '../block-list/use-block-props/use-block-refs'; +import { isDropTargetValid } from '../use-block-drop-zone'; const BlockDraggable = ( { children, @@ -19,16 +22,24 @@ const BlockDraggable = ( { cloneClassname, onDragStart, onDragEnd, + fadeWhenDisabled = false, } ) => { - const { srcRootClientId, isDraggable, icon } = useSelect( + const { + srcRootClientId, + isDraggable, + icon, + visibleInserter, + getBlockType, + } = useSelect( ( select ) => { const { canMoveBlocks, getBlockRootClientId, getBlockName, getBlockAttributes, + isBlockInsertionPointVisible, } = select( blockEditorStore ); - const { getBlockType, getActiveBlockVariation } = + const { getBlockType: _getBlockType, getActiveBlockVariation } = select( blocksStore ); const rootClientId = getBlockRootClientId( clientIds[ 0 ] ); const blockName = getBlockName( clientIds[ 0 ] ); @@ -40,15 +51,21 @@ const BlockDraggable = ( { return { srcRootClientId: rootClientId, isDraggable: canMoveBlocks( clientIds, rootClientId ), - icon: variation?.icon || getBlockType( blockName )?.icon, + icon: variation?.icon || _getBlockType( blockName )?.icon, + visibleInserter: isBlockInsertionPointVisible(), + getBlockType: _getBlockType, }; }, [ clientIds ] ); + const isDragging = useRef( false ); const [ startScrolling, scrollOnDragOver, stopScrolling ] = useScrollWhenDragging(); + const { getAllowedBlocks, getBlockNamesByClientId, getBlockRootClientId } = + useSelect( blockEditorStore ); + const { startDraggingBlocks, stopDraggingBlocks } = useDispatch( blockEditorStore ); @@ -61,6 +78,97 @@ const BlockDraggable = ( { }; }, [] ); + // Find the root of the editor iframe. + const blockRef = useBlockRef( clientIds[ 0 ] ); + const editorRoot = blockRef.current?.closest( 'body' ); + + /* + * Add a dragover event listener to the editor root to track the blocks being dragged over. + * The listener has to be inside the editor iframe otherwise the target isn't accessible. + */ + useEffect( () => { + if ( ! editorRoot || ! fadeWhenDisabled ) { + return; + } + + const onDragOver = ( event ) => { + if ( ! event.target.closest( '[data-block]' ) ) { + return; + } + const draggedBlockNames = getBlockNamesByClientId( clientIds ); + const targetClientId = event.target + .closest( '[data-block]' ) + .getAttribute( 'data-block' ); + + const allowedBlocks = getAllowedBlocks( targetClientId ); + const targetBlockName = getBlockNamesByClientId( [ + targetClientId, + ] )[ 0 ]; + + /* + * Check if the target is valid to drop in. + * If the target's allowedBlocks is an empty array, + * it isn't a container block, in which case we check + * its parent's validity instead. + */ + let dropTargetValid; + if ( allowedBlocks?.length === 0 ) { + const targetRootClientId = + getBlockRootClientId( targetClientId ); + const targetRootBlockName = getBlockNamesByClientId( [ + targetRootClientId, + ] )[ 0 ]; + const rootAllowedBlocks = + getAllowedBlocks( targetRootClientId ); + dropTargetValid = isDropTargetValid( + getBlockType, + rootAllowedBlocks, + draggedBlockNames, + targetRootBlockName + ); + } else { + dropTargetValid = isDropTargetValid( + getBlockType, + allowedBlocks, + draggedBlockNames, + targetBlockName + ); + } + + /* + * Update the body class to reflect if drop target is valid. + * This has to be done on the document body because the draggable + * chip is rendered outside of the editor iframe. + */ + if ( ! dropTargetValid && ! visibleInserter ) { + window?.document?.body?.classList?.add( + 'block-draggable-invalid-drag-token' + ); + } else { + window?.document?.body?.classList?.remove( + 'block-draggable-invalid-drag-token' + ); + } + }; + + const throttledOnDragOver = throttle( onDragOver, 200 ); + + editorRoot.addEventListener( 'dragover', throttledOnDragOver ); + + return () => { + editorRoot.removeEventListener( 'dragover', throttledOnDragOver ); + }; + }, [ + clientIds, + editorRoot, + fadeWhenDisabled, + getAllowedBlocks, + getBlockNamesByClientId, + getBlockRootClientId, + getBlockType, + visibleInserter, + ] ); + if ( ! isDraggable ) { return children( { draggable: false } ); } @@ -102,7 +210,11 @@ const BlockDraggable = ( { } } } __experimentalDragComponent={ - + } > { ( { onDraggableStart, onDraggableEnd } ) => { diff --git a/packages/block-editor/src/components/block-draggable/style.scss b/packages/block-editor/src/components/block-draggable/style.scss index a27d4c4caf2f2..afbf77319f720 100644 --- a/packages/block-editor/src/components/block-draggable/style.scss +++ b/packages/block-editor/src/components/block-draggable/style.scss @@ -14,6 +14,7 @@ display: inline-flex; height: $block-toolbar-height; padding: 0 ( $grid-unit-15 + $border-width ); + position: relative; user-select: none; width: max-content; @@ -45,3 +46,37 @@ font-size: $default-font-size; } } + +// Specificity bump to override native component style. +.block-editor-block-draggable-chip__disabled.block-editor-block-draggable-chip__disabled { + opacity: 0; + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + background-color: transparent; + transition: all 0.1s linear 0.1s; + + .block-editor-block-draggable-chip__disabled-icon { + width: $grid-unit-50 * 0.5; + height: $grid-unit-50 * 0.5; + box-shadow: inset 0 0 0 1.5px $white; + border-radius: 50%; + display: inline-block; + padding: 0; + background: transparent linear-gradient(-45deg, transparent 47.5%, $white 47.5%, $white 52.5%, transparent 52.5%); + + } +} + +.block-draggable-invalid-drag-token { + .block-editor-block-draggable-chip__disabled.block-editor-block-draggable-chip__disabled { + background-color: $gray-700; + opacity: 1; + box-shadow: 0 4px 8px rgba($black, 0.2); + } +} diff --git a/packages/block-editor/src/components/block-mover/index.js b/packages/block-editor/src/components/block-mover/index.js index 9b9f32457561e..328160e944e95 100644 --- a/packages/block-editor/src/components/block-mover/index.js +++ b/packages/block-editor/src/components/block-mover/index.js @@ -64,7 +64,7 @@ function BlockMover( { clientIds, hideDragHandle } ) { } ) } > { ! hideDragHandle && ( - + { ( draggableProps ) => (