-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hide drop indicator where block isn't allowed to drop. #56843
Changes from 11 commits
a2fd12c
761fb61
4f0513f
e0a9753
43a1bc0
359b5b8
59607c9
9e40e4e
3bf8a0f
5270a64
f8aba09
1da2470
f194767
37096af
e9f3a48
240cd39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,30 +5,47 @@ 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 | ||
*/ | ||
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, | ||
clientIds, | ||
cloneClassname, | ||
onDragStart, | ||
onDragEnd, | ||
fadeWhenDisabled = false, | ||
} ) => { | ||
const { srcRootClientId, isDraggable, icon } = useSelect( | ||
const { | ||
srcRootClientId, | ||
isDraggable, | ||
icon, | ||
visibleInserter, | ||
getBlockType, | ||
getAllowedBlocks, | ||
draggedBlockNames, | ||
getBlockNamesByClientId, | ||
} = useSelect( | ||
( select ) => { | ||
const { | ||
canMoveBlocks, | ||
getBlockRootClientId, | ||
getBlockName, | ||
getBlockAttributes, | ||
isBlockInsertionPointVisible, | ||
getDraggedBlockClientIds, | ||
getBlockNamesByClientId: _getBlockNamesByClientId, | ||
getAllowedBlocks: _getAllowedBlocks, | ||
} = select( blockEditorStore ); | ||
const { getBlockType, getActiveBlockVariation } = | ||
const { getBlockType: _getBlockType, getActiveBlockVariation } = | ||
select( blocksStore ); | ||
const rootClientId = getBlockRootClientId( clientIds[ 0 ] ); | ||
const blockName = getBlockName( clientIds[ 0 ] ); | ||
|
@@ -40,11 +57,19 @@ 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, | ||
getAllowedBlocks: _getAllowedBlocks, | ||
draggedBlockNames: _getBlockNamesByClientId( | ||
getDraggedBlockClientIds() | ||
), | ||
getBlockNamesByClientId: _getBlockNamesByClientId, | ||
}; | ||
}, | ||
[ clientIds ] | ||
); | ||
|
||
const isDragging = useRef( false ); | ||
const [ startScrolling, scrollOnDragOver, stopScrolling ] = | ||
useScrollWhenDragging(); | ||
|
@@ -61,6 +86,68 @@ 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. | ||
// Check if the dragged blocks are allowed inside the target. If not, grey out the draggable. | ||
useEffect( () => { | ||
if ( ! editorRoot ) { | ||
return; | ||
} | ||
|
||
const onDragOver = ( event ) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this callback might need to be throttled similar to the code in 2023-12-14.10.37.55.mp4There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hrm yeah I'm wondering how best to do that given the inability to use hooks inside the effect callback 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I ran into this issue when testing, it seemed to crash in Safari and I couldn't move the chip any more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oo wait I see there's a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok the throttle seems to make it better! I set it to the same as draggable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a little better, but it's still lagging a bit for me with the throttle at
Would it work to maybe move defining the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I had a think about this 😄 and realised that we actually don't need to use local state for I also tried increasing the throttle time and it doesn't seem to make things any worse, so might leave it at 200 or so. I just pushed an update, let me know if it improves things at all! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice work! That appears to have fixed up the performance issues for me. Subjectively, without CPU throttling, it's feeling smooth now, and with CPU throttling, it seems much the same as |
||
if ( ! event.target.closest( '[data-block]' ) ) { | ||
return; | ||
} | ||
|
||
const targetClientId = event.target | ||
.closest( '[data-block]' ) | ||
.getAttribute( 'data-block' ); | ||
|
||
const allowedBlocks = getAllowedBlocks( targetClientId ); | ||
const targetBlockName = getBlockNamesByClientId( [ | ||
targetClientId, | ||
] )[ 0 ]; | ||
const 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 ); | ||
}; | ||
}, [ | ||
draggedBlockNames, | ||
editorRoot, | ||
getAllowedBlocks, | ||
getBlockNamesByClientId, | ||
getBlockType, | ||
visibleInserter, | ||
] ); | ||
|
||
if ( ! isDraggable ) { | ||
return children( { draggable: false } ); | ||
} | ||
|
@@ -102,7 +189,14 @@ const BlockDraggable = ( { | |
} | ||
} } | ||
__experimentalDragComponent={ | ||
<BlockDraggableChip count={ clientIds.length } icon={ icon } /> | ||
<BlockDraggableChip | ||
count={ clientIds.length } | ||
icon={ icon } | ||
className={ | ||
fadeWhenDisabled && | ||
'block-editor-block-draggable-chip-fade' | ||
} | ||
/> | ||
} | ||
> | ||
{ ( { onDraggableStart, onDraggableEnd } ) => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,9 @@ | |
padding: 0 ( $grid-unit-15 + $border-width ); | ||
user-select: none; | ||
width: max-content; | ||
transition: | ||
background-color 0.5s linear, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest something much quicker, but with a tiny delay (to reduce the flashes):
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While we're here, a tiny tweak to the chip's shadow makes it feel nice :)
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, added! |
||
opacity 0.5s linear; | ||
|
||
svg { | ||
fill: currentColor; | ||
|
@@ -45,3 +48,10 @@ | |
font-size: $default-font-size; | ||
} | ||
} | ||
|
||
.block-draggable-invalid-drag-token { | ||
.block-editor-block-draggable-chip-fade .block-editor-block-draggable-chip { | ||
background-color: $gray-600; | ||
opacity: 0.8; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this
useEffect
and itsonDragOver
still needed? Since theuseBlockDropZone
is hiding the insertion point, could we replace the whole useEffect with something like:Then the logic of when to show and hide the insertion point could live in one place, so that the
BlockDraggable
doesn't have to also perform checks with its own drag event handler.Or is there another reason why this component needs to perform its own separate checks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, so the crucial bit here is finding the
targetClientId
, which tells us the container that the draggable is currently hovering over. To find that we need adragover
event listener attached to the inside of the editor iframe (if it's on the outside, it won't be able to access the elements inside the iframe). We need thetargetClientId
to check if the drop target is valid, which is necessary because if the chip is hovering over a valid container but there's no drop indicator currently showing (if we're moving between drop areas for instance), we don't want it to grey out.Unfortunately there's not much that can be done to restrict how many times this runs without slowing down the responsiveness of the drag chip to what it's hovering over. The problem here is that, unlike with the drop indicators that exist statically between all blocks so we know where they're placed from the start, we need to find out what the draggable is being dragged over, and afaik the only way to do that is through the event target of a
dragover
listener.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, gotcha! Thanks for the explanation, that makes sense now 👍
Yeah, it can be really tricky to get the right balance when slowing things down so that it's slow enough for performance reasons, but fast enough to feel responsive to a user! 😅
That sounds about right to me, too. If it winds up being a bit of a rabbithole trying to get it working smoothly, would it be worth splitting out the
useBlockDropZone
change and landing that first? Totally cool if you want to keep it all together while figuring everything out of course!