Skip to content

Commit

Permalink
Allow drag and drop to create Rows and Galleries (#56186)
Browse files Browse the repository at this point in the history
* Try dragging blocks onto other blocks to create Rows

* Make blocks draggable onto other blocks

* Change logic to use inbetween inserter

* make it less broken

* Drop to either side of target block

* Improve distances for drop indicator to show and wrap images in gallery

* Use THRESHOLD_DISTANCE

* Fix scrollbar weirdness

* Check blocks actually exist

* Make sure block to group with isn't already a Row.

* Make sure drop indicator doesn't appear outside Rows.

* Check block groupability

* Update tests to take new behaviour into account

* Fix Paragraph e2e tests

* Fix drag and drop e2e tests
  • Loading branch information
tellthemachines committed Jan 24, 2024
1 parent 177c45b commit 973d87b
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 103 deletions.
24 changes: 22 additions & 2 deletions packages/block-editor/src/components/block-popover/inbetween.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ function BlockPopoverInbetween( {
children,
__unstablePopoverSlot,
__unstableContentRef,
operation = 'insert',
nearestSide = 'right',
...props
} ) {
// This is a temporary hack to get the inbetween inserter to recompute properly.
Expand Down Expand Up @@ -81,7 +83,10 @@ function BlockPopoverInbetween( {
return undefined;
}

const contextElement = previousElement || nextElement;
const contextElement =
operation === 'group'
? nextElement || previousElement
: previousElement || nextElement;

return {
contextElement,
Expand All @@ -98,7 +103,20 @@ function BlockPopoverInbetween( {
let width = 0;
let height = 0;

if ( isVertical ) {
if ( operation === 'group' ) {
const targetRect = nextRect || previousRect;
top = targetRect.top;
// No spacing is likely around blocks in this operation.
// So width of the inserter containing rect is set to 0.
width = 0;
height = targetRect.bottom - targetRect.top;
// Popover calculates its distance from mid-block so some
// adjustments are needed to make it appear in the right place.
left =
nearestSide === 'left'
? targetRect.left - 2
: targetRect.right - 2;
} else if ( isVertical ) {
// vertical
top = previousRect ? previousRect.bottom : nextRect.top;
width = previousRect ? previousRect.width : nextRect.width;
Expand Down Expand Up @@ -141,6 +159,8 @@ function BlockPopoverInbetween( {
popoverRecomputeCounter,
isVertical,
isVisible,
operation,
nearestSide,
] );

const popoverScrollRef = usePopoverScroll( __unstableContentRef );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export const InsertionPointOpenRef = createContext();
function InbetweenInsertionPointPopover( {
__unstablePopoverSlot,
__unstableContentRef,
operation = 'insert',
nearestSide = 'right',
} ) {
const { selectBlock, hideInsertionPoint } = useDispatch( blockEditorStore );
const openRef = useContext( InsertionPointOpenRef );
Expand Down Expand Up @@ -138,9 +140,14 @@ function InbetweenInsertionPointPopover( {
return null;
}

const orientationClassname =
orientation === 'horizontal' || operation === 'group'
? 'is-horizontal'
: 'is-vertical';

const className = classnames(
'block-editor-block-list__insertion-point',
'is-' + orientation
orientationClassname
);

return (
Expand All @@ -149,6 +156,8 @@ function InbetweenInsertionPointPopover( {
nextClientId={ nextClientId }
__unstablePopoverSlot={ __unstablePopoverSlot }
__unstableContentRef={ __unstableContentRef }
operation={ operation }
nearestSide={ nearestSide }
>
<motion.div
layout={ ! disableMotion }
Expand Down Expand Up @@ -236,6 +245,10 @@ export default function InsertionPoint( props ) {
{ ...props }
/>
) : (
<InbetweenInsertionPointPopover { ...props } />
<InbetweenInsertionPointPopover
operation={ insertionPoint.operation }
nearestSide={ insertionPoint.nearestSide }
{ ...props }
/>
);
}
79 changes: 60 additions & 19 deletions packages/block-editor/src/components/use-block-drop-zone/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import useOnBlockDrop from '../use-on-block-drop';
import {
getDistanceToNearestEdge,
isPointContainedByRect,
isPointWithinTopAndBottomBoundariesOfRect,
} from '../../utils/math';
import { store as blockEditorStore } from '../../store';

Expand Down Expand Up @@ -72,6 +73,8 @@ export function getDropTargetPosition(
let nearestIndex = 0;
let insertPosition = 'before';
let minDistance = Infinity;
let targetBlockIndex = null;
let nearestSide = 'right';

const {
dropZoneElement,
Expand Down Expand Up @@ -136,20 +139,48 @@ export function getDropTargetPosition(
}

blocksData.forEach(
( { isUnmodifiedDefaultBlock, getBoundingClientRect, blockIndex } ) => {
( {
isUnmodifiedDefaultBlock,
getBoundingClientRect,
blockIndex,
blockOrientation,
} ) => {
const rect = getBoundingClientRect();

let [ distance, edge ] = getDistanceToNearestEdge(
position,
rect,
allowedEdges
);
// If the the point is close to a side, prioritize that side.
const [ sideDistance, sideEdge ] = getDistanceToNearestEdge(
position,
rect,
[ 'left', 'right' ]
);

const isPointInsideRect = isPointContainedByRect( position, rect );

// Prioritize the element if the point is inside of an unmodified default block.
if (
isUnmodifiedDefaultBlock &&
isPointContainedByRect( position, rect )
) {
if ( isUnmodifiedDefaultBlock && isPointInsideRect ) {
distance = 0;
} else if (
orientation === 'vertical' &&
blockOrientation !== 'horizontal' &&
( ( isPointInsideRect && sideDistance < THRESHOLD_DISTANCE ) ||
( ! isPointInsideRect &&
isPointWithinTopAndBottomBoundariesOfRect(
position,
rect
) ) )
) {
/**
* This condition should only apply when the layout is vertical (otherwise there's
* no need to create a Row) and dropzones should only activate when the block is
* either within and close to the sides of the target block or on its outer sides.
*/
targetBlockIndex = blockIndex;
nearestSide = sideEdge;
}

if ( distance < minDistance ) {
Expand All @@ -175,6 +206,10 @@ export function getDropTargetPosition(
const isAdjacentBlockUnmodifiedDefaultBlock =
!! blocksData[ adjacentIndex ]?.isUnmodifiedDefaultBlock;

// If the target index is set then group with the block at that index.
if ( targetBlockIndex !== null ) {
return [ targetBlockIndex, 'group', nearestSide ];
}
// If both blocks are not unmodified default blocks then just insert between them.
if (
! isNearestBlockUnmodifiedDefaultBlock &&
Expand Down Expand Up @@ -284,6 +319,7 @@ export default function useBlockDropZone( {
dropTarget.index,
{
operation: dropTarget.operation,
nearestSide: dropTarget.nearestSide,
}
);
const throttled = useThrottle(
Expand Down Expand Up @@ -333,28 +369,32 @@ export default function useBlockDropZone( {
.getElementById( `block-${ clientId }` )
.getBoundingClientRect(),
blockIndex: getBlockIndex( clientId ),
blockOrientation:
getBlockListSettings( clientId )?.orientation,
};
} );

const [ targetIndex, operation ] = getDropTargetPosition(
blocksData,
{ x: event.clientX, y: event.clientY },
getBlockListSettings( targetRootClientId )?.orientation,
{
dropZoneElement,
parentBlockClientId,
parentBlockOrientation: parentBlockClientId
? getBlockListSettings( parentBlockClientId )
?.orientation
: undefined,
rootBlockIndex: getBlockIndex( targetRootClientId ),
}
);
const [ targetIndex, operation, nearestSide ] =
getDropTargetPosition(
blocksData,
{ x: event.clientX, y: event.clientY },
getBlockListSettings( targetRootClientId )?.orientation,
{
dropZoneElement,
parentBlockClientId,
parentBlockOrientation: parentBlockClientId
? getBlockListSettings( parentBlockClientId )
?.orientation
: undefined,
rootBlockIndex: getBlockIndex( targetRootClientId ),
}
);

registry.batch( () => {
setDropTarget( {
index: targetIndex,
operation,
nearestSide,
} );

const insertionPointClientId = [
Expand All @@ -366,6 +406,7 @@ export default function useBlockDropZone( {

showInsertionPoint( insertionPointClientId, targetIndex, {
operation,
nearestSide,
} );
} );
},
Expand Down
Loading

1 comment on commit 973d87b

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in 973d87b.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/7636021859
📝 Reported issues:

Please sign in to comment.