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 d7d053be179fa6..cca21e7e0b7858 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 0b8f3c2d87f520..7191a0f1428a92 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 a27d4c4caf2f29..afbf77319f7200 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 9b9f32457561e6..328160e944e954 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 ) => (