Skip to content

Commit

Permalink
Try locking the x-axis to the indent level
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewserong committed Feb 6, 2024
1 parent 7ddb858 commit efbee86
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 22 deletions.
26 changes: 21 additions & 5 deletions packages/block-editor/src/components/list-view/block-contents.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import ListViewBlockSelectButton from './block-select-button';
import BlockDraggable from '../block-draggable';
import { store as blockEditorStore } from '../../store';
import { useListViewContext } from './context';
import useDragChip from './use-drag-chip';

const ListViewBlockContents = forwardRef(
(
Expand Down Expand Up @@ -49,11 +50,20 @@ const ListViewBlockContents = forwardRef(

const {
AdditionalBlockContent,
blockDropTarget,
insertedBlock,
listViewInstanceId,
setInsertedBlock,
treeGridElementRef,
} = useListViewContext();

const { dragChipOnDragStart, dragChipOnDragEnd } = useDragChip( {
blockDropTarget,
cloneClassname: 'block-editor-list-view-draggable-chip',
listViewRef: treeGridElementRef,
elementId: `list-view-${ listViewInstanceId }-block-${ clientId }`,
} );

const isBlockMoveTarget =
blockMovingClientId && selectedBlockInBlockEditor === clientId;

Expand Down Expand Up @@ -81,9 +91,9 @@ const ListViewBlockContents = forwardRef(
<BlockDraggable
appendToOwnerDocument
clientIds={ draggableClientIds }
cloneClassname={ 'block-editor-list-view-draggable-chip' }
dragComponent={ null }
elementId={ `list-view-${ listViewInstanceId }-block-${ clientId }` }
cloneClassname={
'block-editor-list-view-default-draggable-chip'
}
>
{ ( { draggable, onDragStart, onDragEnd } ) => (
<ListViewBlockSelectButton
Expand All @@ -97,8 +107,14 @@ const ListViewBlockContents = forwardRef(
siblingBlockCount={ siblingBlockCount }
level={ level }
draggable={ draggable }
onDragStart={ onDragStart }
onDragEnd={ onDragEnd }
onDragStart={ ( event ) => {
onDragStart( event );
dragChipOnDragStart( event );
} }
onDragEnd={ ( event ) => {
onDragEnd( event );
dragChipOnDragEnd( event );
} }
isExpanded={ isExpanded }
{ ...props }
/>
Expand Down
2 changes: 2 additions & 0 deletions packages/block-editor/src/components/list-view/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ function ListViewComponent(
const contextValue = useMemo(
() => ( {
blockDropPosition,
blockDropTarget,
blockDropTargetIndex,
blockIndexes,
draggedClientIds,
Expand All @@ -292,6 +293,7 @@ function ListViewComponent(
} ),
[
blockDropPosition,
blockDropTarget,
blockDropTargetIndex,
blockIndexes,
draggedClientIds,
Expand Down
5 changes: 5 additions & 0 deletions packages/block-editor/src/components/list-view/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,11 @@
// Indent is a full icon size, plus 4px which optically aligns child icons to the text label above.
$block-navigation-max-indent: 8;

.block-editor-list-view-default-draggable-chip {
// Hide the default draggable chip
display: none;
}

.block-editor-list-view-draggable-chip {
.block-editor-list-view-leaf {
background-color: $white;
Expand Down
231 changes: 231 additions & 0 deletions packages/block-editor/src/components/list-view/use-drag-chip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/**
* WordPress dependencies
*/
import { throttle } from '@wordpress/compose';
import { useEffect, useMemo, useRef, useState } from '@wordpress/element';

const dragImageClass = 'components-draggable__invisible-drag-image';
const cloneWrapperClass = 'components-draggable__clone';
const clonePadding = 0;
const bodyClass = 'is-dragging-components-draggable';

export default function useDragChip( {
blockDropTarget,
cloneClassname,
listViewRef,
elementId,
transferData,
__experimentalTransferDataType: transferDataType = 'text',
} ) {
const { clientId, rootClientId } = blockDropTarget || {};
// const horizontalOffset = useRef( 0 );
const targetAriaLevelRef = useRef( 1 );
const originalAriaLevelRef = useRef( 1 );
const cleanup = useRef( () => {} );
const [ isWithinListView, setIsWithinListView ] = useState( false );

// TODO: Add RTL support.
// const rtl = isRTL();

const [ rootBlockElement ] = useMemo( () => {
if ( ! listViewRef.current ) {
return [];
}
// The rootClientId will be defined whenever dropping into inner
// block lists, but is undefined when dropping at the root level.
const _rootBlockElement = rootClientId
? listViewRef.current.querySelector(
`[data-block="${ rootClientId }"]`
)
: undefined;
// The clientId represents the sibling block, the dragged block will
// usually be inserted adjacent to it. It will be undefined when
// dropping a block into an empty block list.
const _blockElement = clientId
? listViewRef.current.querySelector(
`[data-block="${ clientId }"]`
)
: undefined;
return [ _rootBlockElement, _blockElement ];
}, [ listViewRef, rootClientId, clientId ] );

useEffect( () => {
let ariaLevel = 1;

if ( rootBlockElement ) {
const _ariaLevel = parseInt(
rootBlockElement.getAttribute( 'aria-level' ),
10
);

ariaLevel = _ariaLevel ? _ariaLevel + 1 : 1;
}

targetAriaLevelRef.current = ariaLevel;
}, [ rootBlockElement ] );

/**
* Removes the element clone, resets cursor, and removes drag listener.
*
* @param {DragEvent} event The non-custom DragEvent.
*/
function end( event ) {
event.preventDefault();
cleanup.current();
}

/**
* This method does a couple of things:
*
* - Clones the current element and spawns clone over original element.
* - Adds a fake temporary drag image to avoid browser defaults.
* - Sets transfer data.
* - Adds dragover listener.
*
* @param {DragEvent} event The non-custom DragEvent.
*/
function start( event ) {
const { ownerDocument } = event.target;

event.dataTransfer.setData(
transferDataType,
JSON.stringify( transferData )
);

const cloneWrapper = ownerDocument.createElement( 'div' );
// Reset position to 0,0. Natural stacking order will position this lower, even with a transform otherwise.
cloneWrapper.style.top = '0';
cloneWrapper.style.left = '0';

const dragImage = ownerDocument.createElement( 'div' );

// Set a fake drag image to avoid browser defaults. Remove from DOM
// right after. event.dataTransfer.setDragImage is not supported yet in
// IE, we need to check for its existence first.
if ( 'function' === typeof event.dataTransfer.setDragImage ) {
dragImage.classList.add( dragImageClass );
ownerDocument.body.appendChild( dragImage );
event.dataTransfer.setDragImage( dragImage, 0, 0 );
}

cloneWrapper.classList.add( cloneWrapperClass );

if ( cloneClassname ) {
cloneWrapper.classList.add( cloneClassname );
}

let x = 0;
let y = 0;

const element = ownerDocument.getElementById( elementId );

const _originalAriaLevel = element.getAttribute( 'aria-level' );

if ( _originalAriaLevel ) {
originalAriaLevelRef.current = parseInt( _originalAriaLevel, 10 );
}

// Prepare element clone and append to element wrapper.
const elementRect = element.getBoundingClientRect();
const elementTopOffset = elementRect.top;
const elementLeftOffset = elementRect.left;

cloneWrapper.style.width = `${
elementRect.width + clonePadding * 2
}px`;

const clone = element.cloneNode( true );
clone.id = `clone-${ elementId }`;

// Position clone right over the original element (20px padding).
x = elementLeftOffset - clonePadding;
y = elementTopOffset - clonePadding;
cloneWrapper.style.transform = `translate( ${ x }px, ${ y }px )`;

// Hack: Remove iFrames as it's causing the embeds drag clone to freeze.
Array.from( clone.querySelectorAll( 'iframe' ) ).forEach( ( child ) =>
child.parentNode?.removeChild( child )
);

cloneWrapper.appendChild( clone );

ownerDocument.body.appendChild( cloneWrapper );

// Mark the current cursor coordinates.
let cursorLeft = event.clientX;
let cursorTop = event.clientY;

function over( e ) {
if ( listViewRef.current ) {
if (
! isWithinListView &&
listViewRef.current.contains( e.target )
) {
setIsWithinListView( true );
} else if (
isWithinListView &&
! listViewRef.current.contains( e.target )
) {
setIsWithinListView( false );
}
}

// Skip doing any work if mouse has not moved.
if ( cursorLeft === e.clientX && cursorTop === e.clientY ) {
return;
}

const horizontalOffset =
( targetAriaLevelRef.current - originalAriaLevelRef.current ) *
28;

const nextY = y + e.clientY - cursorTop;
const nextX = x + horizontalOffset;

cloneWrapper.style.transform = `translate( ${ nextX }px, ${ nextY }px )`;
cursorLeft = e.clientX;
cursorTop = e.clientY;
// x = nextX;
y = nextY;
}

// Aim for 60fps (16 ms per frame) for now. We can potentially use requestAnimationFrame (raf) instead,
// note that browsers may throttle raf below 60fps in certain conditions.
// @ts-ignore
const throttledDragOver = throttle( over, 16 );

ownerDocument.addEventListener( 'dragover', throttledDragOver );

// Update cursor to 'grabbing', document wide.
ownerDocument.body.classList.add( bodyClass );

cleanup.current = () => {
// Remove drag clone.
if ( cloneWrapper && cloneWrapper.parentNode ) {
cloneWrapper.parentNode.removeChild( cloneWrapper );
}

if ( dragImage && dragImage.parentNode ) {
dragImage.parentNode.removeChild( dragImage );
}

// Reset cursor.
ownerDocument.body.classList.remove( bodyClass );

ownerDocument.removeEventListener( 'dragover', throttledDragOver );
};
}

useEffect(
() => () => {
cleanup.current();
},
[]
);

return {
dragChipOnDragStart: start,
dragChipOnDragEnd: end,
isWithinListView,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ export default function useListViewDropZone( {
const throttled = useThrottle(
useCallback(
( event, currentTarget ) => {
let position = { x: event.clientX, y: event.clientY };
const position = { x: event.clientX, y: event.clientY };
const isBlockDrag = !! draggedBlockClientIds?.length;

const blockElements = Array.from(
Expand Down Expand Up @@ -531,22 +531,6 @@ export default function useListViewDropZone( {
};
} );

const { ownerDocument } = currentTarget || {};
const dragChipBlockElement = ownerDocument?.querySelector(
'.block-editor-list-view-draggable-chip .block-editor-block-icon'
);

if ( dragChipBlockElement ) {
const dragChipBlockRect =
dragChipBlockElement.getBoundingClientRect();
position = {
x: rtl
? dragChipBlockRect.right
: dragChipBlockRect.left,
y: dragChipBlockRect.top + dragChipBlockRect.height / 2,
};
}

const newTarget = getListViewDropTarget(
blocksData,
position,
Expand Down

0 comments on commit efbee86

Please sign in to comment.