From c1be69ef3d473b392c6fab83db89166309d64332 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:12:20 +1100 Subject: [PATCH 01/32] Include earlier experiment to make drag chip resemble the list view item --- .../src/components/block-draggable/index.js | 21 +++++++++++++---- .../components/list-view/block-contents.js | 15 +++++++++--- .../src/components/list-view/style.scss | 23 +++++++++++++++++++ 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/block-draggable/index.js b/packages/block-editor/src/components/block-draggable/index.js index 7191a0f1428a92..c86bc55e8ed079 100644 --- a/packages/block-editor/src/components/block-draggable/index.js +++ b/packages/block-editor/src/components/block-draggable/index.js @@ -17,12 +17,15 @@ import { __unstableUseBlockRef as useBlockRef } from '../block-list/use-block-pr import { isDropTargetValid } from '../use-block-drop-zone'; const BlockDraggable = ( { + appendToOwnerDocument, children, clientIds, cloneClassname, + elementId, onDragStart, onDragEnd, fadeWhenDisabled = false, + dragComponent, } ) => { const { srcRootClientId, @@ -181,6 +184,7 @@ const BlockDraggable = ( { return ( + // Check against `undefined` so that `null` can be used to disable + // the default drag component. + dragComponent !== undefined ? ( + dragComponent + ) : ( + + ) } + elementId={ elementId } > { ( { onDraggableStart, onDraggableEnd } ) => { return children( { diff --git a/packages/block-editor/src/components/list-view/block-contents.js b/packages/block-editor/src/components/list-view/block-contents.js index 8d5b03395f3e20..875d60371f25d3 100644 --- a/packages/block-editor/src/components/list-view/block-contents.js +++ b/packages/block-editor/src/components/list-view/block-contents.js @@ -47,8 +47,12 @@ const ListViewBlockContents = forwardRef( [] ); - const { AdditionalBlockContent, insertedBlock, setInsertedBlock } = - useListViewContext(); + const { + AdditionalBlockContent, + insertedBlock, + listViewInstanceId, + setInsertedBlock, + } = useListViewContext(); const isBlockMoveTarget = blockMovingClientId && selectedBlockInBlockEditor === clientId; @@ -74,7 +78,12 @@ const ListViewBlockContents = forwardRef( setInsertedBlock={ setInsertedBlock } /> ) } - + { ( { draggable, onDragStart, onDragEnd } ) => ( Date: Wed, 29 Nov 2023 15:32:01 +1100 Subject: [PATCH 02/32] Try displacing list view items via transform: translateY while dragging --- .../src/components/list-view/block.js | 3 ++ .../src/components/list-view/branch.js | 28 ++++++++++++++- .../src/components/list-view/index.js | 35 ++++++++++++++++--- .../src/components/list-view/style.scss | 18 +++++++++- .../list-view/use-list-view-block-indexes.js | 29 +++++++++++++++ .../list-view/use-list-view-drop-zone.js | 1 + 6 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 packages/block-editor/src/components/list-view/use-list-view-block-indexes.js diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index d756f28e43127f..6d0c1bc8cb77e2 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -43,6 +43,7 @@ import AriaReferencedText from './aria-referenced-text'; function ListViewBlock( { block: { clientId }, + displacement, isDragged, isSelected, isBranchSelected, @@ -270,6 +271,8 @@ function ListViewBlock( { 'has-single-cell': ! showBlockActions, 'is-synced': blockInformation?.isSynced, 'is-draggable': canMove, + 'is-above': displacement === 'above', + 'is-below': displacement === 'below', } ); // Only include all selected blocks if the currently clicked on block diff --git a/packages/block-editor/src/components/list-view/branch.js b/packages/block-editor/src/components/list-view/branch.js index e2e27f5f2cb5af..afc452c8d2e6fb 100644 --- a/packages/block-editor/src/components/list-view/branch.js +++ b/packages/block-editor/src/components/list-view/branch.js @@ -115,7 +115,12 @@ function ListViewBranch( props ) { [ parentId ] ); - const { expandedState, draggedClientIds } = useListViewContext(); + const { + blockDropTargetIndex, + blockIndexes, + expandedState, + draggedClientIds, + } = useListViewContext(); if ( ! canParentExpand ) { return null; @@ -143,6 +148,25 @@ function ListViewBranch( props ) { ); } + let displacement; + + if ( blockDropTargetIndex !== undefined ) { + const thisBlockIndex = blockIndexes[ clientId ]; + + if ( thisBlockIndex !== undefined ) { + displacement = + thisBlockIndex < blockDropTargetIndex + ? 'above' + : 'below'; + } + + if ( thisBlockIndex === 0 && blockDropTargetIndex === 0 ) { + // If the block is the first block and the drop target is also the first block, + // then the block is being dragged above the first block. + displacement = 'below'; + } + } + const { itemInView } = fixedListWindow; const blockInView = itemInView( nextPosition ); @@ -199,6 +223,7 @@ function ListViewBranch( props ) { listPosition={ nextPosition } selectedClientIds={ selectedClientIds } isSyncedBranch={ syncedBranch } + displacement={ displacement } /> ) } { ! showBlock && ( @@ -221,6 +246,7 @@ function ListViewBranch( props ) { selectedClientIds={ selectedClientIds } isExpanded={ isExpanded } isSyncedBranch={ syncedBranch } + displacement={ displacement } /> ) } diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index 315a6153839d16..d84e435af1fe94 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -30,6 +30,7 @@ import ListViewBranch from './branch'; import { ListViewContext } from './context'; import ListViewDropIndicator from './drop-indicator'; import useBlockSelection from './use-block-selection'; +import useListViewBlockIndexes from './use-list-view-block-indexes'; import useListViewClientIds from './use-list-view-client-ids'; import useListViewDropZone from './use-list-view-drop-zone'; import useListViewExpandSelectedItem from './use-list-view-expand-selected-item'; @@ -68,6 +69,7 @@ export const BLOCK_LIST_ITEM_HEIGHT = 36; * @param {?boolean} props.showBlockMovers Flag to enable block movers. Defaults to `false`. * @param {?boolean} props.isExpanded Flag to determine whether nested levels are expanded by default. Defaults to `false`. * @param {?boolean} props.showAppender Flag to show or hide the block appender. Defaults to `false`. + * @param {?boolean} props.showDropIndicator Flag to show or hide the drop indicator line. Defaults to `false`. * @param {?ComponentType} props.blockSettingsMenu Optional more menu substitution. Defaults to the standard `BlockSettingsDropdown` component. * @param {string} props.rootClientId The client id of the root block from which we determine the blocks to show in the list. * @param {string} props.description Optional accessible description for the tree grid component. @@ -83,6 +85,7 @@ function ListViewComponent( showBlockMovers = false, isExpanded = false, showAppender = false, + showDropIndicator = false, blockSettingsMenu: BlockSettingsMenu = BlockSettingsDropdown, rootClientId, description, @@ -105,6 +108,7 @@ function ListViewComponent( const instanceId = useInstanceId( ListViewComponent ); const { clientIdsTree, draggedClientIds, selectedClientIds } = useListViewClientIds( { blocks, rootClientId } ); + const blockIndexes = useListViewBlockIndexes( clientIdsTree ); const { getBlock } = useSelect( blockEditorStore ); const { visibleBlockCount, shouldShowInnerBlocks } = useSelect( @@ -210,8 +214,27 @@ function ListViewComponent( [ updateBlockSelection ] ); + const blockDropTargetIndex = useMemo( () => { + if ( ! blockDropTarget?.clientId ) { + return undefined; + } + const foundBlockIndex = blockIndexes[ blockDropTarget.clientId ]; + + if ( + foundBlockIndex === undefined || + blockDropTarget?.dropPosition === 'top' + ) { + return foundBlockIndex; + } + + // If dragging below or inside the block, treat the drop target as the next block. + return foundBlockIndex + 1; + }, [ blockDropTarget, blockIndexes ] ); + const contextValue = useMemo( () => ( { + blockDropTargetIndex, + blockIndexes, draggedClientIds, expandedState, expand, @@ -225,6 +248,8 @@ function ListViewComponent( rootClientId, } ), [ + blockDropTargetIndex, + blockIndexes, draggedClientIds, expandedState, expand, @@ -267,10 +292,12 @@ function ListViewComponent( return ( - + { showDropIndicator && ( + + ) } { description && ( { description } diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index e2a90e17196743..fe2f735f9fcb94 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -14,6 +14,8 @@ .block-editor-list-view-leaf { // Use position relative for row animation. position: relative; + transition: transform 0.2s; + transform: translateY(0); &.is-draggable, &.is-draggable .block-editor-list-view-block-contents { @@ -128,6 +130,20 @@ border-radius: 0; } + &.is-above { + transition: transform 0.2s; + transform: translateY(-18px) !important; + } + + &.is-below { + transition: transform 0.2s; + transform: translateY(18px) !important; + } + + &.is-dragging { + display: none; + } + // List View renders a fixed number of items and relies on each item having a fixed height of 36px. // If this value changes, we should also change the itemHeight value set in useFixedWindowList. // See: https://github.com/WordPress/gutenberg/pull/35230 for additional context. @@ -376,7 +392,7 @@ background: $white; border: 1px solid $gray-400; border-radius: $radius-block-ui; - opacity: 0.75; + opacity: 1; .block-editor-list-view-block__contents-cell { flex: 1; diff --git a/packages/block-editor/src/components/list-view/use-list-view-block-indexes.js b/packages/block-editor/src/components/list-view/use-list-view-block-indexes.js new file mode 100644 index 00000000000000..5890e366d7ce9a --- /dev/null +++ b/packages/block-editor/src/components/list-view/use-list-view-block-indexes.js @@ -0,0 +1,29 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + +export default function useListViewBlockIndexes( blocks ) { + const blockIndexes = useMemo( () => { + const indexes = {}; + + let currentGlobalIndex = 0; + + const traverseBlocks = ( blockList ) => { + blockList.forEach( ( block ) => { + indexes[ block.clientId ] = currentGlobalIndex; + currentGlobalIndex++; + + if ( block.innerBlocks.length > 0 ) { + traverseBlocks( block.innerBlocks ); + } + } ); + }; + + traverseBlocks( blocks ); + + return indexes; + }, [ blocks ] ); + + return blockIndexes; +} diff --git a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js index a1a369d3f94084..290073bee53d35 100644 --- a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js +++ b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js @@ -304,6 +304,7 @@ export function getListViewDropTarget( blocksData, position, rtl = false ) { return { rootClientId: candidateBlockData.clientId, + clientId: candidateBlockData.clientId, blockIndex: newBlockIndex, dropPosition: 'inside', }; From 671ed67a77a18a52b2ff85283bb98fbe0cbdf74c Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:20:06 +1100 Subject: [PATCH 03/32] Try adding a bit of styling when nesting --- .../src/components/list-view/block.js | 2 ++ .../src/components/list-view/branch.js | 8 +++++- .../src/components/list-view/index.js | 28 +++++++++++-------- .../src/components/list-view/style.scss | 16 +++++++++++ 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index 6d0c1bc8cb77e2..3da2b95e2606ae 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -45,6 +45,7 @@ function ListViewBlock( { block: { clientId }, displacement, isDragged, + isNesting, isSelected, isBranchSelected, selectBlock, @@ -273,6 +274,7 @@ function ListViewBlock( { 'is-draggable': canMove, 'is-above': displacement === 'above', 'is-below': displacement === 'below', + 'is-nesting': isNesting, } ); // Only include all selected blocks if the currently clicked on block diff --git a/packages/block-editor/src/components/list-view/branch.js b/packages/block-editor/src/components/list-view/branch.js index afc452c8d2e6fb..d099433d2e6f95 100644 --- a/packages/block-editor/src/components/list-view/branch.js +++ b/packages/block-editor/src/components/list-view/branch.js @@ -116,6 +116,7 @@ function ListViewBranch( props ) { ); const { + blockDropPosition, blockDropTargetIndex, blockIndexes, expandedState, @@ -149,6 +150,7 @@ function ListViewBranch( props ) { } let displacement; + let isNesting; if ( blockDropTargetIndex !== undefined ) { const thisBlockIndex = blockIndexes[ clientId ]; @@ -165,6 +167,10 @@ function ListViewBranch( props ) { // then the block is being dragged above the first block. displacement = 'below'; } + + isNesting = + blockDropTargetIndex - 1 === thisBlockIndex && + blockDropPosition === 'inside'; } const { itemInView } = fixedListWindow; @@ -224,6 +230,7 @@ function ListViewBranch( props ) { selectedClientIds={ selectedClientIds } isSyncedBranch={ syncedBranch } displacement={ displacement } + isNesting={ isNesting } /> ) } { ! showBlock && ( @@ -246,7 +253,6 @@ function ListViewBranch( props ) { selectedClientIds={ selectedClientIds } isExpanded={ isExpanded } isSyncedBranch={ syncedBranch } - displacement={ displacement } /> ) } diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index d84e435af1fe94..f351778603c617 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -214,25 +214,28 @@ function ListViewComponent( [ updateBlockSelection ] ); - const blockDropTargetIndex = useMemo( () => { - if ( ! blockDropTarget?.clientId ) { - return undefined; - } - const foundBlockIndex = blockIndexes[ blockDropTarget.clientId ]; + const { blockDropTargetIndex, blockDropPosition } = useMemo( () => { + let _blockDropTargetIndex; - if ( - foundBlockIndex === undefined || - blockDropTarget?.dropPosition === 'top' - ) { - return foundBlockIndex; + if ( blockDropTarget?.clientId ) { + const foundBlockIndex = blockIndexes[ blockDropTarget.clientId ]; + // If dragging below or inside the block, treat the drop target as the next block. + _blockDropTargetIndex = + foundBlockIndex === undefined || + blockDropTarget?.dropPosition === 'top' + ? foundBlockIndex + : foundBlockIndex + 1; } - // If dragging below or inside the block, treat the drop target as the next block. - return foundBlockIndex + 1; + return { + blockDropTargetIndex: _blockDropTargetIndex, + blockDropPosition: blockDropTarget?.dropPosition, + }; }, [ blockDropTarget, blockIndexes ] ); const contextValue = useMemo( () => ( { + blockDropPosition, blockDropTargetIndex, blockIndexes, draggedClientIds, @@ -248,6 +251,7 @@ function ListViewComponent( rootClientId, } ), [ + blockDropPosition, blockDropTargetIndex, blockIndexes, draggedClientIds, diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index fe2f735f9fcb94..890d3cd17ba185 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -14,6 +14,8 @@ .block-editor-list-view-leaf { // Use position relative for row animation. position: relative; + // Set the transition so that once dragged items are dropped, the other items + // smoothly return to their original positions. transition: transform 0.2s; transform: translateY(0); @@ -130,6 +132,20 @@ border-radius: 0; } + &.is-nesting td { + background: rgba(var(--wp-admin-theme-color--rgb), 0.5); + } + + &.is-nesting td:first-child { + border-top-left-radius: $radius-block-ui; + border-bottom-left-radius: $radius-block-ui; + } + + &.is-nesting td:last-child { + border-top-right-radius: $radius-block-ui; + border-bottom-right-radius: $radius-block-ui; + } + &.is-above { transition: transform 0.2s; transform: translateY(-18px) !important; From 44c1962ca22337a90cff40bbd0db1892b5c03b0e Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:53:39 +1100 Subject: [PATCH 04/32] Try to fix tests --- .../src/components/list-view/test/use-list-view-drop-zone.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/block-editor/src/components/list-view/test/use-list-view-drop-zone.js b/packages/block-editor/src/components/list-view/test/use-list-view-drop-zone.js index 98c2b31132c60e..f1180a9fa27ca1 100644 --- a/packages/block-editor/src/components/list-view/test/use-list-view-drop-zone.js +++ b/packages/block-editor/src/components/list-view/test/use-list-view-drop-zone.js @@ -127,6 +127,7 @@ describe( 'getListViewDropTarget', () => { expect( target ).toEqual( { blockIndex: 0, + clientId: 'block-1', dropPosition: 'inside', rootClientId: 'block-1', } ); @@ -142,6 +143,7 @@ describe( 'getListViewDropTarget', () => { expect( target ).toEqual( { blockIndex: 0, + clientId: 'block-3', dropPosition: 'inside', rootClientId: 'block-3', } ); @@ -244,6 +246,7 @@ describe( 'getListViewDropTarget', () => { expect( target ).toEqual( { blockIndex: 1, + clientId: 'block-1', dropPosition: 'inside', rootClientId: 'block-1', } ); @@ -277,6 +280,7 @@ describe( 'getListViewDropTarget', () => { expect( target ).toEqual( { blockIndex: 0, + clientId: 'block-1', dropPosition: 'inside', rootClientId: 'block-1', } ); From f6d14a4e6c6a5a272596fd6a428bd9897dc824e7 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 1 Dec 2023 16:27:29 +1100 Subject: [PATCH 05/32] Offset the top of the table to match the displacement --- .../block-editor/src/components/list-view/index.js | 11 ++++++++++- .../block-editor/src/components/list-view/style.scss | 7 +++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index f351778603c617..0dd4097c09ae0a 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -309,7 +314,11 @@ function ListViewComponent( ) } 0 && + blockDropTargetIndex !== undefined, + } ) } aria-label={ __( 'Block navigation structure' ) } ref={ treeGridRef } onCollapseRow={ collapseRow } diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 890d3cd17ba185..3bd6b0c56b4766 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -9,6 +9,13 @@ margin: (-$grid-unit-15) (-$grid-unit-15 * 0.5) 0; width: calc(100% + #{ $grid-unit-15 }); } + + transition: transform 0.2s; + + &.is-dragging { + transition: transform 0.2s; + transform: translateY(18px) !important; + } } .block-editor-list-view-leaf { From 775c3ab84ab55cc5fc548d49527cdaefb4486fdd Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Mon, 4 Dec 2023 11:59:06 +1100 Subject: [PATCH 06/32] Improve the experience when beginning to drag a block --- .../src/components/list-view/branch.js | 28 +++++++---- .../src/components/list-view/index.js | 49 +++++++++++++------ .../src/components/list-view/leaf.js | 20 ++++---- .../src/components/list-view/style.scss | 14 ++---- 4 files changed, 66 insertions(+), 45 deletions(-) diff --git a/packages/block-editor/src/components/list-view/branch.js b/packages/block-editor/src/components/list-view/branch.js index d099433d2e6f95..e50f1cae7681a9 100644 --- a/packages/block-editor/src/components/list-view/branch.js +++ b/packages/block-editor/src/components/list-view/branch.js @@ -118,6 +118,7 @@ function ListViewBranch( props ) { const { blockDropPosition, blockDropTargetIndex, + firstDraggedBlockIndex, blockIndexes, expandedState, draggedClientIds, @@ -152,20 +153,26 @@ function ListViewBranch( props ) { let displacement; let isNesting; - if ( blockDropTargetIndex !== undefined ) { + if ( + blockDropTargetIndex !== undefined && + firstDraggedBlockIndex !== undefined + ) { const thisBlockIndex = blockIndexes[ clientId ]; if ( thisBlockIndex !== undefined ) { - displacement = + if ( + thisBlockIndex >= firstDraggedBlockIndex && thisBlockIndex < blockDropTargetIndex - ? 'above' - : 'below'; - } + ) { + displacement = 'above'; + } - if ( thisBlockIndex === 0 && blockDropTargetIndex === 0 ) { - // If the block is the first block and the drop target is also the first block, - // then the block is being dragged above the first block. - displacement = 'below'; + if ( + thisBlockIndex < firstDraggedBlockIndex && + thisBlockIndex >= blockDropTargetIndex + ) { + displacement = 'below'; + } } isNesting = @@ -173,6 +180,9 @@ function ListViewBranch( props ) { blockDropPosition === 'inside'; } + // TODO: Ensure above / below classes are output when dragging outside of the list view, + // otherwise a gap appears. + const { itemInView } = fixedListWindow; const blockInView = itemInView( nextPosition ); diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index 0dd4097c09ae0a..62b5aa0ecd96a7 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -219,24 +219,39 @@ function ListViewComponent( [ updateBlockSelection ] ); - const { blockDropTargetIndex, blockDropPosition } = useMemo( () => { - let _blockDropTargetIndex; + const firstDraggedBlockClientId = draggedClientIds?.[ 0 ]; - if ( blockDropTarget?.clientId ) { - const foundBlockIndex = blockIndexes[ blockDropTarget.clientId ]; - // If dragging below or inside the block, treat the drop target as the next block. - _blockDropTargetIndex = - foundBlockIndex === undefined || - blockDropTarget?.dropPosition === 'top' - ? foundBlockIndex - : foundBlockIndex + 1; - } + const { blockDropTargetIndex, blockDropPosition, firstDraggedBlockIndex } = + useMemo( () => { + let _blockDropTargetIndex, _firstDraggedBlockIndex; - return { - blockDropTargetIndex: _blockDropTargetIndex, - blockDropPosition: blockDropTarget?.dropPosition, - }; - }, [ blockDropTarget, blockIndexes ] ); + if ( blockDropTarget?.clientId ) { + const foundBlockIndex = + blockIndexes[ blockDropTarget.clientId ]; + // If dragging below or inside the block, treat the drop target as the next block. + _blockDropTargetIndex = + foundBlockIndex === undefined || + blockDropTarget?.dropPosition === 'top' + ? foundBlockIndex + : foundBlockIndex + 1; + } + + if ( firstDraggedBlockClientId ) { + const foundBlockIndex = + blockIndexes[ firstDraggedBlockClientId ]; + _firstDraggedBlockIndex = + foundBlockIndex === undefined || + blockDropTarget?.dropPosition === 'top' + ? foundBlockIndex + : foundBlockIndex + 1; + } + + return { + blockDropTargetIndex: _blockDropTargetIndex, + blockDropPosition: blockDropTarget?.dropPosition, + firstDraggedBlockIndex: _firstDraggedBlockIndex, + }; + }, [ blockDropTarget, blockIndexes, firstDraggedBlockClientId ] ); const contextValue = useMemo( () => ( { @@ -246,6 +261,7 @@ function ListViewComponent( draggedClientIds, expandedState, expand, + firstDraggedBlockIndex, collapse, BlockSettingsMenu, listViewInstanceId: instanceId, @@ -262,6 +278,7 @@ function ListViewComponent( draggedClientIds, expandedState, expand, + firstDraggedBlockIndex, collapse, BlockSettingsMenu, instanceId, diff --git a/packages/block-editor/src/components/list-view/leaf.js b/packages/block-editor/src/components/list-view/leaf.js index c42d696f32b3c1..2610e063730477 100644 --- a/packages/block-editor/src/components/list-view/leaf.js +++ b/packages/block-editor/src/components/list-view/leaf.js @@ -8,13 +8,13 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __experimentalTreeGridRow as TreeGridRow } from '@wordpress/components'; -import { useMergeRefs } from '@wordpress/compose'; +// import { useMergeRefs } from '@wordpress/compose'; import { forwardRef } from '@wordpress/element'; /** * Internal dependencies */ -import useMovingAnimation from '../use-moving-animation'; +// import useMovingAnimation from '../use-moving-animation'; const AnimatedTreeGridRow = animated( TreeGridRow ); @@ -32,18 +32,18 @@ const ListViewLeaf = forwardRef( }, ref ) => { - const animationRef = useMovingAnimation( { - isSelected, - adjustScrolling: false, - enableAnimation: true, - triggerAnimationOnChange: path, - } ); + // const animationRef = useMovingAnimation( { + // isSelected, + // adjustScrolling: false, + // enableAnimation: true, + // triggerAnimationOnChange: path, + // } ); - const mergedRef = useMergeRefs( [ ref, animationRef ] ); + // const mergedRef = useMergeRefs( [ ref, animationRef ] ); return ( Date: Mon, 4 Dec 2023 12:52:38 +1100 Subject: [PATCH 07/32] Smoothly hide gap when dragging outside of the list view area --- .../src/components/list-view/block.js | 4 +-- .../src/components/list-view/branch.js | 36 ++++++++++++++++--- .../src/components/list-view/index.js | 2 ++ .../src/components/list-view/style.scss | 4 +-- .../list-view/use-list-view-drop-zone.js | 8 ++++- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index 3da2b95e2606ae..0c68506d528f0c 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -272,8 +272,8 @@ function ListViewBlock( { 'has-single-cell': ! showBlockActions, 'is-synced': blockInformation?.isSynced, 'is-draggable': canMove, - 'is-above': displacement === 'above', - 'is-below': displacement === 'below', + 'is-displacement-up': displacement === 'up', + 'is-displacement-down': displacement === 'down', 'is-nesting': isNesting, } ); diff --git a/packages/block-editor/src/components/list-view/branch.js b/packages/block-editor/src/components/list-view/branch.js index e50f1cae7681a9..60710bca2a93b6 100644 --- a/packages/block-editor/src/components/list-view/branch.js +++ b/packages/block-editor/src/components/list-view/branch.js @@ -153,35 +153,61 @@ function ListViewBranch( props ) { let displacement; let isNesting; + // Determine where to displace the position of the current block, relative + // to the blocks being dragged (in their original position) and the drop target + // (the position where a user is currently dragging the blocks to). if ( blockDropTargetIndex !== undefined && + blockDropTargetIndex !== null && firstDraggedBlockIndex !== undefined ) { + // If the block is being dragged and there is a valid drop target, + // determine if the block being rendered should be displaced up or down. const thisBlockIndex = blockIndexes[ clientId ]; if ( thisBlockIndex !== undefined ) { + // If the current block appears after the set of dragged blocks + // (in their original position), but is before the drop target, + // then the current block should be displaced up. if ( thisBlockIndex >= firstDraggedBlockIndex && thisBlockIndex < blockDropTargetIndex ) { - displacement = 'above'; + displacement = 'up'; } + // If the current block appears before the set of dragged blocks + // (in their original position), but is after the drop target, + // then the current block should be displaced down. if ( thisBlockIndex < firstDraggedBlockIndex && thisBlockIndex >= blockDropTargetIndex ) { - displacement = 'below'; + displacement = 'down'; } } isNesting = blockDropTargetIndex - 1 === thisBlockIndex && blockDropPosition === 'inside'; - } + } else if ( + blockDropTargetIndex === null && + firstDraggedBlockIndex !== undefined + ) { + // A `null` value for `blockDropTargetIndex` indicates that the + // drop target is outside of the valid areas within the list view. + // In this case, the drag is still active, but as there is no + // valid drop target, we should remove the gap indicating where + // the block would be inserted. + const thisBlockIndex = blockIndexes[ clientId ]; - // TODO: Ensure above / below classes are output when dragging outside of the list view, - // otherwise a gap appears. + if ( + thisBlockIndex !== undefined && + thisBlockIndex >= firstDraggedBlockIndex + ) { + displacement = 'up'; + } + } const { itemInView } = fixedListWindow; const blockInView = itemInView( nextPosition ); diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index 62b5aa0ecd96a7..afd2095d0329cb 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -234,6 +234,8 @@ function ListViewComponent( blockDropTarget?.dropPosition === 'top' ? foundBlockIndex : foundBlockIndex + 1; + } else if ( blockDropTarget === null ) { + _blockDropTargetIndex = null; } if ( firstDraggedBlockClientId ) { diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index e7710cd6534fbe..10d21fc7364610 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -146,12 +146,12 @@ border-bottom-right-radius: $radius-block-ui; } - &.is-above { + &.is-displacement-up { transition: transform 0.2s; transform: translateY(-36px) !important; } - &.is-below { + &.is-displacement-down { transition: transform 0.2s; transform: translateY(36px) !important; } diff --git a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js index 290073bee53d35..8e00f08836b40e 100644 --- a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js +++ b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js @@ -497,6 +497,9 @@ export default function useListViewDropZone( { dropZoneElement } ) { }, onDragLeave() { throttled.cancel(); + // Use `null` value to indicate that the drop target is not valid, + // but that the drag is still active. This allows for styling rules + // that are active only when a user drags outside of the list view. setTarget( null ); }, onDragOver( event ) { @@ -507,7 +510,10 @@ export default function useListViewDropZone( { dropZoneElement } ) { }, onDragEnd() { throttled.cancel(); - setTarget( null ); + // Use `undefined` value to indicate that the drag has concluded. + // This allows styling rules that are active only when a user is + // dragging to be removed. + setTarget( undefined ); }, } ); From cd228f94752516eceac0daa218f8a027a1148a51 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 5 Dec 2023 15:10:52 +1100 Subject: [PATCH 08/32] Merge in changes from drag to collapsed block to expand PR --- .../src/components/list-view/index.js | 2 + .../src/components/list-view/style.scss | 26 +------ .../list-view/use-list-view-drop-zone.js | 73 ++++++++++++++++++- 3 files changed, 73 insertions(+), 28 deletions(-) diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index afd2095d0329cb..33a37361f01319 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -141,6 +141,8 @@ function ListViewComponent( const { ref: dropZoneRef, target: blockDropTarget } = useListViewDropZone( { dropZoneElement, + expandedState, + setExpandedState, } ); const elementRef = useRef(); const treeGridRef = useMergeRefs( [ elementRef, dropZoneRef, ref ] ); diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 10d21fc7364610..1be22ee9c82ef3 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -132,20 +132,6 @@ border-radius: 0; } - &.is-nesting td { - background: rgba(var(--wp-admin-theme-color--rgb), 0.5); - } - - &.is-nesting td:first-child { - border-top-left-radius: $radius-block-ui; - border-bottom-left-radius: $radius-block-ui; - } - - &.is-nesting td:last-child { - border-top-right-radius: $radius-block-ui; - border-bottom-right-radius: $radius-block-ui; - } - &.is-displacement-up { transition: transform 0.2s; transform: translateY(-36px) !important; @@ -192,6 +178,7 @@ } } + &.is-nesting .block-editor-list-view-block-contents, .block-editor-list-view-block-contents:focus { box-shadow: none; @@ -206,11 +193,6 @@ box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); z-index: 2; pointer-events: none; - - // Hide focus styles while a user is dragging blocks/files etc. - .is-dragging-components-draggable & { - box-shadow: none; - } } } @@ -219,14 +201,10 @@ right: 0; } + &.is-nesting .block-editor-list-view__menu, .block-editor-list-view-block__menu:focus { box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); z-index: 1; - - // Hide focus styles while a user is dragging blocks/files etc. - .is-dragging-components-draggable & { - box-shadow: none; - } } &.is-visible .block-editor-list-view-block-contents { diff --git a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js index 8e00f08836b40e..47ccdc8a1d1f88 100644 --- a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js +++ b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js @@ -2,10 +2,11 @@ * WordPress dependencies */ import { useSelect } from '@wordpress/data'; -import { useState, useCallback } from '@wordpress/element'; +import { useState, useCallback, useEffect } from '@wordpress/element'; import { useThrottle, __experimentalUseDropZone as useDropZone, + usePrevious, } from '@wordpress/compose'; import { isRTL } from '@wordpress/i18n'; @@ -397,15 +398,30 @@ export function getListViewDropTarget( blocksData, position, rtl = false ) { }; } +// Throttle options need to be defined outside of the hook to avoid +// re-creating the object on every render. This is due to a limitation +// of the `useThrottle` hook, where the options object is included +// in the dependency array for memoization. +const EXPAND_THROTTLE_OPTIONS = { + leading: false, // Don't call the function immediately on the first call. + trailing: true, // Do call the function on the last call. +}; + /** * A react hook for implementing a drop zone in list view. * - * @param {Object} props Named parameters. - * @param {?HTMLElement} [props.dropZoneElement] Optional element to be used as the drop zone. + * @param {Object} props Named parameters. + * @param {?HTMLElement} [props.dropZoneElement] Optional element to be used as the drop zone. + * @param {Object} [props.expandedState] The expanded state of the blocks in the list view. + * @param {Function} [props.setExpandedState] Function to set the expanded state of a list of block clientIds. * * @return {WPListViewDropZoneTarget} The drop target. */ -export default function useListViewDropZone( { dropZoneElement } ) { +export default function useListViewDropZone( { + dropZoneElement, + expandedState, + setExpandedState, +} ) { const { getBlockRootClientId, getBlockIndex, @@ -421,6 +437,55 @@ export default function useListViewDropZone( { dropZoneElement } ) { const rtl = isRTL(); + const previousRootClientId = usePrevious( targetRootClientId ); + + const maybeExpandBlock = useCallback( + ( _expandedState, _target ) => { + // If the user is attempting to drop a block inside a collapsed block, + // that is, using a nesting gesture flagged by 'inside' dropPosition, + // expand the block within the list view, if it isn't already. + const { rootClientId } = _target || {}; + if ( ! rootClientId ) { + return; + } + if ( + _target?.dropPosition === 'inside' && + ! _expandedState[ rootClientId ] + ) { + setExpandedState( { + type: 'expand', + clientIds: [ rootClientId ], + } ); + } + }, + [ setExpandedState ] + ); + + // Throttle the maybeExpandBlock function to avoid expanding the block + // too quickly when the user is dragging over the block. This is to + // avoid expanding the block when the user is just passing over it. + const throttledMaybeExpandBlock = useThrottle( + maybeExpandBlock, + 500, + EXPAND_THROTTLE_OPTIONS + ); + + useEffect( () => { + if ( + target?.dropPosition !== 'inside' || + previousRootClientId !== target?.rootClientId + ) { + throttledMaybeExpandBlock.cancel(); + return; + } + throttledMaybeExpandBlock( expandedState, target ); + }, [ + expandedState, + previousRootClientId, + target, + throttledMaybeExpandBlock, + ] ); + const draggedBlockClientIds = getDraggedBlockClientIds(); const throttled = useThrottle( useCallback( From 5892875c87d03c3453e47af5bd96483a5d46b2af Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 5 Dec 2023 16:39:55 +1100 Subject: [PATCH 09/32] Try collapsing selected blocks down to a single row space while dragging --- .../src/components/list-view/block.js | 2 + .../src/components/list-view/branch.js | 106 +++++++++--------- .../src/components/list-view/index.js | 9 ++ .../src/components/list-view/style.scss | 20 +++- 4 files changed, 81 insertions(+), 56 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index 0c68506d528f0c..350e0c6532f34a 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -44,6 +44,7 @@ import AriaReferencedText from './aria-referenced-text'; function ListViewBlock( { block: { clientId }, displacement, + isAfterDraggedBlocks, isDragged, isNesting, isSelected, @@ -274,6 +275,7 @@ function ListViewBlock( { 'is-draggable': canMove, 'is-displacement-up': displacement === 'up', 'is-displacement-down': displacement === 'down', + 'is-after-dragged-blocks': isAfterDraggedBlocks, 'is-nesting': isNesting, } ); diff --git a/packages/block-editor/src/components/list-view/branch.js b/packages/block-editor/src/components/list-view/branch.js index 60710bca2a93b6..67cf9534b207a3 100644 --- a/packages/block-editor/src/components/list-view/branch.js +++ b/packages/block-editor/src/components/list-view/branch.js @@ -91,7 +91,6 @@ function ListViewBranch( props ) { selectedClientIds, level = 1, path = '', - isBranchDragged = false, isBranchSelected = false, listPosition = 0, fixedListWindow, @@ -150,63 +149,65 @@ function ListViewBranch( props ) { ); } + const isDragged = !! draggedClientIds?.includes( clientId ); + let displacement; let isNesting; + let isAfterDraggedBlocks; - // Determine where to displace the position of the current block, relative - // to the blocks being dragged (in their original position) and the drop target - // (the position where a user is currently dragging the blocks to). - if ( - blockDropTargetIndex !== undefined && - blockDropTargetIndex !== null && - firstDraggedBlockIndex !== undefined - ) { - // If the block is being dragged and there is a valid drop target, - // determine if the block being rendered should be displaced up or down. + if ( firstDraggedBlockIndex !== undefined ) { const thisBlockIndex = blockIndexes[ clientId ]; + isAfterDraggedBlocks = + thisBlockIndex > firstDraggedBlockIndex && ! isDragged; - if ( thisBlockIndex !== undefined ) { - // If the current block appears after the set of dragged blocks - // (in their original position), but is before the drop target, - // then the current block should be displaced up. - if ( - thisBlockIndex >= firstDraggedBlockIndex && - thisBlockIndex < blockDropTargetIndex - ) { - displacement = 'up'; + // Determine where to displace the position of the current block, relative + // to the blocks being dragged (in their original position) and the drop target + // (the position where a user is currently dragging the blocks to). + if ( + blockDropTargetIndex !== undefined && + blockDropTargetIndex !== null + ) { + // If the block is being dragged and there is a valid drop target, + // determine if the block being rendered should be displaced up or down. + + if ( thisBlockIndex !== undefined ) { + // If the current block appears after the set of dragged blocks + // (in their original position), but is before the drop target, + // then the current block should be displaced up. + if ( + thisBlockIndex >= firstDraggedBlockIndex && + thisBlockIndex < blockDropTargetIndex + ) { + displacement = 'up'; + } + + // If the current block appears before the set of dragged blocks + // (in their original position), but is after the drop target, + // then the current block should be displaced down. + if ( + thisBlockIndex < firstDraggedBlockIndex && + thisBlockIndex >= blockDropTargetIndex + ) { + displacement = 'down'; + } } - // If the current block appears before the set of dragged blocks - // (in their original position), but is after the drop target, - // then the current block should be displaced down. + isNesting = + blockDropTargetIndex - 1 === thisBlockIndex && + blockDropPosition === 'inside'; + } else if ( blockDropTargetIndex === null ) { + // A `null` value for `blockDropTargetIndex` indicates that the + // drop target is outside of the valid areas within the list view. + // In this case, the drag is still active, but as there is no + // valid drop target, we should remove the gap indicating where + // the block would be inserted. if ( - thisBlockIndex < firstDraggedBlockIndex && - thisBlockIndex >= blockDropTargetIndex + thisBlockIndex !== undefined && + thisBlockIndex >= firstDraggedBlockIndex ) { - displacement = 'down'; + displacement = 'up'; } } - - isNesting = - blockDropTargetIndex - 1 === thisBlockIndex && - blockDropPosition === 'inside'; - } else if ( - blockDropTargetIndex === null && - firstDraggedBlockIndex !== undefined - ) { - // A `null` value for `blockDropTargetIndex` indicates that the - // drop target is outside of the valid areas within the list view. - // In this case, the drag is still active, but as there is no - // valid drop target, we should remove the gap indicating where - // the block would be inserted. - const thisBlockIndex = blockIndexes[ clientId ]; - - if ( - thisBlockIndex !== undefined && - thisBlockIndex >= firstDraggedBlockIndex - ) { - displacement = 'up'; - } } const { itemInView } = fixedListWindow; @@ -224,8 +225,6 @@ function ListViewBranch( props ) { ? expandedState[ clientId ] ?? isExpanded : undefined; - const isDragged = !! draggedClientIds?.includes( clientId ); - // Make updates to the selected or dragged blocks synchronous, // but asynchronous for any other block. const isSelected = isClientIdSelected( @@ -244,7 +243,6 @@ function ListViewBranch( props ) { const showBlock = isDragged || blockInView || - isBranchDragged || ( isSelected && clientId === selectedClientIds[ 0 ] ); return ( @@ -254,18 +252,19 @@ function ListViewBranch( props ) { selectBlock={ selectBlock } isSelected={ isSelected } isBranchSelected={ isSelectedBranch } - isDragged={ isDragged || isBranchDragged } + isDragged={ isDragged } level={ level } position={ position } rowCount={ rowCount } siblingBlockCount={ blockCount } showBlockMovers={ showBlockMovers } path={ updatedPath } - isExpanded={ shouldExpand } + isExpanded={ isDragged ? false : shouldExpand } listPosition={ nextPosition } selectedClientIds={ selectedClientIds } isSyncedBranch={ syncedBranch } displacement={ displacement } + isAfterDraggedBlocks={ isAfterDraggedBlocks } isNesting={ isNesting } /> ) } @@ -274,7 +273,7 @@ function ListViewBranch( props ) { ) } - { hasNestedBlocks && shouldExpand && ( + { hasNestedBlocks && shouldExpand && ! isDragged && ( Date: Thu, 7 Dec 2023 15:38:28 +1100 Subject: [PATCH 10/32] Fix jumpiness in Safari --- packages/block-editor/src/components/list-view/style.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index c976da18b5482f..09a07473e691bc 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -14,9 +14,8 @@ .block-editor-list-view-leaf { // Use position relative for row animation. position: relative; - // Set the transition so that once dragged items are dropped, the other items - // smoothly return to their original positions. - transition: transform 0.2s; + // Set the initial translate position to ensure the UI in Safari doesn't jump + // when rows later receive the is-displacement-up or is-displacement-down class. transform: translateY(0); &.is-draggable, From 2f723ef82badeed089ae7498408381aed7ddd1da Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:04:34 +1100 Subject: [PATCH 11/32] Try making styling match the selected state --- .../src/components/list-view/style.scss | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 09a07473e691bc..d26750b9facc68 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -53,7 +53,7 @@ &.is-selected.is-synced td { background: var(--wp-block-synced-color); } - &.is-synced:not(.is-selected) .block-editor-list-view-block-contents { + &.is-synced:not(.is-selected):not([id^="clone-"]) .block-editor-list-view-block-contents { &:hover, &:focus, .block-editor-block-icon { @@ -89,15 +89,19 @@ } // Border radius for corners of the selected item. + &[id^="clone-"] td:first-child, &.is-first-selected td:first-child { border-top-left-radius: $radius-block-ui; } + &[id^="clone-"] td:last-child, &.is-first-selected td:last-child { border-top-right-radius: $radius-block-ui; } + &[id^="clone-"] td:first-child, &.is-last-selected td:first-child { border-bottom-left-radius: $radius-block-ui; } + &[id^="clone-"] td:last-child, &.is-last-selected td:last-child { border-bottom-right-radius: $radius-block-ui; } @@ -396,20 +400,22 @@ } &[id^="clone-"] { - align-items: center; display: flex; height: 36px; - background: $white; - border: 1px solid $gray-400; - border-radius: $radius-block-ui; opacity: 1; .block-editor-list-view-block__contents-cell { flex: 1; } - &.is-selected td { - background: $white; + .block-editor-list-view-block__menu-cell { + display: flex; + align-items: center; + } + + td { + background: var(--wp-admin-theme-color); + color: $white; .block-editor-list-view-block-select-button, .components-button.has-icon { From 5e6809b0eeef08e23af2bc65a6cf4690e3d847f4 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 8 Dec 2023 14:48:54 +1100 Subject: [PATCH 12/32] Try smoothly dropping to the final position --- .../src/components/list-view/block.js | 1 + .../src/components/list-view/leaf.js | 24 ++++++---- .../src/components/list-view/style.scss | 8 +++- .../components/use-moving-animation/index.js | 46 +++++++++++-------- packages/components/src/draggable/index.tsx | 1 + 5 files changed, 51 insertions(+), 29 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index 350e0c6532f34a..23f3a7cea3c655 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -303,6 +303,7 @@ function ListViewBlock( { return ( { - // const animationRef = useMovingAnimation( { - // isSelected, - // adjustScrolling: false, - // enableAnimation: true, - // triggerAnimationOnChange: path, - // } ); + const animationRef = useMovingAnimation( { + isSelected, + adjustScrolling: false, + enableAnimation: true, + triggerAnimationOnChange: path, + elementSelector: isDragged + ? '.components-draggable-cloned-element' + : undefined, + } ); - // const mergedRef = useMergeRefs( [ ref, animationRef ] ); + const mergedRef = useMergeRefs( [ ref, animationRef ] ); return ( ( { - previous: ref.current && getAbsolutePosition( ref.current ), - prevRect: ref.current && ref.current.getBoundingClientRect(), - } ), + const { prevRect } = useMemo( + () => { + let previousPosition; + + if ( ref.current && elementSelector ) { + const { ownerDocument } = ref.current; + const element = ownerDocument.querySelector( elementSelector ); + if ( element ) { + previousPosition = element.getBoundingClientRect(); + } + } else if ( ref.current ) { + previousPosition = ref.current.getBoundingClientRect(); + } + + return { + prevRect: previousPosition, + }; + }, // eslint-disable-next-line react-hooks/exhaustive-deps [ triggerAnimationOnChange ] ); useLayoutEffect( () => { - if ( ! previous || ! ref.current ) { + if ( ! prevRect || ! ref.current ) { return; } @@ -133,10 +144,10 @@ function useMovingAnimation( { triggerAnimationOnChange, clientId } ) { } ); ref.current.style.transform = undefined; - const destination = getAbsolutePosition( ref.current ); + const destination = ref.current.getBoundingClientRect(); - const x = Math.round( previous.left - destination.left ); - const y = Math.round( previous.top - destination.top ); + const x = Math.round( prevRect.left - destination.left ); + const y = Math.round( prevRect.top - destination.top ); controller.start( { x: 0, y: 0, from: { x, y } } ); @@ -144,7 +155,6 @@ function useMovingAnimation( { triggerAnimationOnChange, clientId } ) { controller.stop(); }; }, [ - previous, prevRect, clientId, isTyping, diff --git a/packages/components/src/draggable/index.tsx b/packages/components/src/draggable/index.tsx index 0a3000538dbf24..e49fe0792adeed 100644 --- a/packages/components/src/draggable/index.tsx +++ b/packages/components/src/draggable/index.tsx @@ -160,6 +160,7 @@ export function Draggable( { const clone = element.cloneNode( true ) as HTMLElement; clone.id = `clone-${ elementId }`; + clone.classList.add( 'components-draggable-cloned-element' ); // Position clone right over the original element (20px padding). x = elementLeftOffset - clonePadding; From cf616ffaa8b46675fcd85ec16b3db7477b45f6c7 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 12 Dec 2023 15:17:07 +1100 Subject: [PATCH 13/32] Fix transitions above dragged items --- .../src/components/list-view/block.js | 1 + .../src/components/list-view/branch.js | 24 ++++++++++--------- .../src/components/list-view/style.scss | 5 ++++ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index 23f3a7cea3c655..aab9d444a5948f 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -273,6 +273,7 @@ function ListViewBlock( { 'has-single-cell': ! showBlockActions, 'is-synced': blockInformation?.isSynced, 'is-draggable': canMove, + 'is-displacement-normal': displacement === 'normal', 'is-displacement-up': displacement === 'up', 'is-displacement-down': displacement === 'down', 'is-after-dragged-blocks': isAfterDraggedBlocks, diff --git a/packages/block-editor/src/components/list-view/branch.js b/packages/block-editor/src/components/list-view/branch.js index 67cf9534b207a3..4d10bcd3d2370f 100644 --- a/packages/block-editor/src/components/list-view/branch.js +++ b/packages/block-editor/src/components/list-view/branch.js @@ -155,10 +155,10 @@ function ListViewBranch( props ) { let isNesting; let isAfterDraggedBlocks; - if ( firstDraggedBlockIndex !== undefined ) { + if ( firstDraggedBlockIndex !== undefined && ! isDragged ) { const thisBlockIndex = blockIndexes[ clientId ]; isAfterDraggedBlocks = - thisBlockIndex > firstDraggedBlockIndex && ! isDragged; + thisBlockIndex > firstDraggedBlockIndex; // Determine where to displace the position of the current block, relative // to the blocks being dragged (in their original position) and the drop target @@ -171,24 +171,24 @@ function ListViewBranch( props ) { // determine if the block being rendered should be displaced up or down. if ( thisBlockIndex !== undefined ) { - // If the current block appears after the set of dragged blocks - // (in their original position), but is before the drop target, - // then the current block should be displaced up. if ( thisBlockIndex >= firstDraggedBlockIndex && thisBlockIndex < blockDropTargetIndex ) { + // If the current block appears after the set of dragged blocks + // (in their original position), but is before the drop target, + // then the current block should be displaced up. displacement = 'up'; - } - - // If the current block appears before the set of dragged blocks - // (in their original position), but is after the drop target, - // then the current block should be displaced down. - if ( + } else if ( thisBlockIndex < firstDraggedBlockIndex && thisBlockIndex >= blockDropTargetIndex ) { + // If the current block appears before the set of dragged blocks + // (in their original position), but is after the drop target, + // then the current block should be displaced down. displacement = 'down'; + } else { + displacement = 'normal'; } } @@ -206,6 +206,8 @@ function ListViewBranch( props ) { thisBlockIndex >= firstDraggedBlockIndex ) { displacement = 'up'; + } else { + displacement = 'normal'; } } } diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 4f3752b4e6d728..89eea7cfa3f5f9 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -135,6 +135,11 @@ border-radius: 0; } + &.is-displacement-normal { + transition: transform 0.2s; + transform: translateY(0); + } + &.is-displacement-up { transition: transform 0.2s; transform: translateY(-36px); From d9eb4cabc5c7dbbb1f03df71b2055209ece27901 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:53:33 +1100 Subject: [PATCH 14/32] Fix flickering issue in Safari --- packages/block-editor/src/components/list-view/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index be0152002921f9..9d6f66cef531bf 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -355,6 +355,13 @@ function ListViewComponent( ( draggedClientIds.length - 1 ) }px` : null, + // Without this, when dragging over list view items, Safari calls onDropZoneLeave + // causing flickering in the position of the drop indicator. + // https://bugs.webkit.org/show_bug.cgi?id=66547 + // See: https://github.com/WordPress/gutenberg/pull/56625 + pointerEvents: draggedClientIds?.length + ? 'none' + : undefined, } } > From c46879b60d09b2fa2802fea490f784f7fd70beac Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Mon, 8 Jan 2024 15:32:11 +1100 Subject: [PATCH 15/32] Try a transparent background color --- packages/block-editor/src/components/list-view/style.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 89eea7cfa3f5f9..6c807df0666f23 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -412,6 +412,8 @@ &[id^="clone-"] { display: flex; + // Where possible, restrict the width of the cloned row to the width of the list view. + max-width: 338px; height: 36px; opacity: 1; @@ -425,8 +427,7 @@ } td { - background: var(--wp-admin-theme-color); - color: $white; + background-color: rgba(var(--wp-admin-theme-color--rgb), 0.04); .block-editor-list-view-block-select-button, .components-button.has-icon { From 416303b1b5e002dce61dd5a9795eaf651e285df9 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Mon, 8 Jan 2024 15:52:24 +1100 Subject: [PATCH 16/32] Fix drag behaviour in navigation block --- packages/block-editor/src/components/list-view/index.js | 7 ------- packages/block-editor/src/components/list-view/style.scss | 8 ++++++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index 9d6f66cef531bf..be0152002921f9 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -355,13 +355,6 @@ function ListViewComponent( ( draggedClientIds.length - 1 ) }px` : null, - // Without this, when dragging over list view items, Safari calls onDropZoneLeave - // causing flickering in the position of the drop indicator. - // https://bugs.webkit.org/show_bug.cgi?id=66547 - // See: https://github.com/WordPress/gutenberg/pull/56625 - pointerEvents: draggedClientIds?.length - ? 'none' - : undefined, } } > diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 6c807df0666f23..6b5f82e0933be0 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -9,6 +9,14 @@ margin: (-$grid-unit-15) (-$grid-unit-15 * 0.5) 0; width: calc(100% + #{ $grid-unit-15 }); } + + // Without setting `pointer-events: none`, when dragging over list view items, + // Safari calls onDropZoneLeave causing flickering in the position of the drop indicator. + // https://bugs.webkit.org/show_bug.cgi?id=66547 + // See: https://github.com/WordPress/gutenberg/pull/56625 + &.is-dragging tbody { + pointer-events: none; + } } .block-editor-list-view-leaf { From e887ba649df6e29adaabc33398317694ffabecab Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 9 Jan 2024 14:33:23 +1100 Subject: [PATCH 17/32] Add vertical drop indicator --- .../list-view/drop-indicator-vertical.js | 165 ++++++++++++++++++ .../src/components/list-view/index.js | 8 +- .../src/components/list-view/style.scss | 12 ++ .../list-view/use-list-view-drop-zone.js | 18 +- 4 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 packages/block-editor/src/components/list-view/drop-indicator-vertical.js diff --git a/packages/block-editor/src/components/list-view/drop-indicator-vertical.js b/packages/block-editor/src/components/list-view/drop-indicator-vertical.js new file mode 100644 index 00000000000000..b9171958e253a7 --- /dev/null +++ b/packages/block-editor/src/components/list-view/drop-indicator-vertical.js @@ -0,0 +1,165 @@ +/** + * WordPress dependencies + */ +import { Popover } from '@wordpress/components'; +import { getScrollContainer } from '@wordpress/dom'; +import { useCallback, useMemo } from '@wordpress/element'; +import { isRTL } from '@wordpress/i18n'; + +const DROP_INDICATOR_WIDTH = 4; + +export default function ListViewDropIndicatorVertical( { + listViewRef, + blockDropTarget, +} ) { + const { rootClientId, clientId, dropPosition } = blockDropTarget || {}; + + const [ rootBlockElement, blockElement ] = 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 ] ); + + // The targetElement is the element that the drop indicator will appear + // before or after. When dropping into an empty block list, blockElement + // is undefined, so the indicator will appear after the rootBlockElement. + const targetElement = blockElement || rootBlockElement; + + const rtl = isRTL(); + + const getDropIndicatorIndent = useCallback( + ( targetElementRect ) => { + if ( ! rootBlockElement ) { + return 0; + } + + // Calculate the indent using the block icon of the root block. + // Using a classname selector here might be flaky and could be + // improved. + const rootBlockIconElement = rootBlockElement.querySelector( + '.block-editor-block-icon' + ); + const rootBlockIconRect = + rootBlockIconElement.getBoundingClientRect(); + return rtl + ? targetElementRect.right - rootBlockIconRect.left + : rootBlockIconRect.right - targetElementRect.left; + }, + [ rootBlockElement, rtl ] + ); + + const popoverAnchor = useMemo( () => { + const isValidDropPosition = + dropPosition === 'top' || + dropPosition === 'bottom' || + dropPosition === 'inside'; + if ( ! targetElement || ! isValidDropPosition ) { + return undefined; + } + + return { + contextElement: targetElement, + getBoundingClientRect() { + const rect = targetElement.getBoundingClientRect(); + const indent = getDropIndicatorIndent( rect ); + // In RTL languages, the drop indicator should be positioned + // to the left of the target element, with the width of the + // indicator determining the indent at the right edge of the + // target element. In LTR languages, the drop indicator should + // end at the right edge of the target element, with the indent + // added to the position of the left edge of the target element. + let left = rtl ? rect.left : rect.left + indent; + let top = 0; + + // In deeply nested lists, where a scrollbar is present, + // the width of the drop indicator should be the width of + // the visible area of the scroll container. Additionally, + // the left edge of the drop indicator line needs to be + // offset by the distance the left edge of the target element + // and the left edge of the scroll container. The ensures + // that the drop indicator position never breaks out of the + // visible area of the scroll container. + const scrollContainer = getScrollContainer( + targetElement, + 'horizontal' + ); + + const doc = targetElement.ownerDocument; + const windowScroll = + scrollContainer === doc.body || + scrollContainer === doc.documentElement; + + // If the scroll container is not the window, offset the left position, if need be. + if ( scrollContainer && ! windowScroll ) { + const scrollContainerRect = + scrollContainer.getBoundingClientRect(); + + // In RTL languages, a vertical scrollbar is present on the + // left edge of the scroll container. The width of the + // scrollbar needs to be accounted for when positioning the + // drop indicator. + const scrollbarWidth = rtl + ? scrollContainer.offsetWidth - + scrollContainer.clientWidth + : 0; + + if ( left < scrollContainerRect.left + scrollbarWidth ) { + left = scrollContainerRect.left + scrollbarWidth; + } + } + + if ( dropPosition === 'top' ) { + top = rect.top; + top -= rect.height * 2; + } else { + // `dropPosition` is either `bottom` or `inside` + top = rect.top; + } + + const height = rect.height; + + return new window.DOMRect( + left, + top, + DROP_INDICATOR_WIDTH, + height + ); + }, + }; + }, [ targetElement, dropPosition, getDropIndicatorIndent, rtl ] ); + + if ( ! targetElement ) { + return null; + } + + return ( + +
+ + ); +} diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index be0152002921f9..4e18f80dd5a526 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -34,6 +34,7 @@ import { __ } from '@wordpress/i18n'; import ListViewBranch from './branch'; import { ListViewContext } from './context'; import ListViewDropIndicator from './drop-indicator'; +import ListViewDropIndicatorVertical from './drop-indicator-vertical'; import useBlockSelection from './use-block-selection'; import useListViewBlockIndexes from './use-list-view-block-indexes'; import useListViewClientIds from './use-list-view-client-ids'; @@ -322,11 +323,16 @@ function ListViewComponent( return ( - { showDropIndicator && ( + { showDropIndicator ? ( + ) : ( + ) } { description && ( diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 6b5f82e0933be0..52b1e527611704 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -518,6 +518,18 @@ $block-navigation-max-indent: 8; } } +.block-editor-list-view-drop-indicator--vertical { + pointer-events: none; + + .block-editor-list-view-drop-indicator__line { + background: var(--wp-admin-theme-color); + padding: 4px 0; + height: 28px; + width: 4px; + border-radius: 4px; + } +} + .block-editor-list-view-placeholder { padding: 0; margin: 0; diff --git a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js index 47ccdc8a1d1f88..71ca979b84dfbc 100644 --- a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js +++ b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js @@ -490,7 +490,7 @@ export default function useListViewDropZone( { const throttled = useThrottle( useCallback( ( event, currentTarget ) => { - const position = { x: event.clientX, y: event.clientY }; + let position = { x: event.clientX, y: event.clientY }; const isBlockDrag = !! draggedBlockClientIds?.length; const blockElements = Array.from( @@ -531,6 +531,22 @@ export default function useListViewDropZone( { }; } ); + const { ownerDocument } = currentTarget || {}; + const dragChipBlockElement = ownerDocument?.querySelector( + '.components-draggable-cloned-element .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, From 7e148bbb5678f163becf60dd0b2156ef3075a252 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 10 Jan 2024 14:08:26 +1100 Subject: [PATCH 18/32] Try using existing drop indicator line --- .../list-view/drop-indicator-vertical.js | 165 ------------------ .../components/list-view/drop-indicator.js | 9 +- .../src/components/list-view/index.js | 18 +- 3 files changed, 7 insertions(+), 185 deletions(-) delete mode 100644 packages/block-editor/src/components/list-view/drop-indicator-vertical.js diff --git a/packages/block-editor/src/components/list-view/drop-indicator-vertical.js b/packages/block-editor/src/components/list-view/drop-indicator-vertical.js deleted file mode 100644 index b9171958e253a7..00000000000000 --- a/packages/block-editor/src/components/list-view/drop-indicator-vertical.js +++ /dev/null @@ -1,165 +0,0 @@ -/** - * WordPress dependencies - */ -import { Popover } from '@wordpress/components'; -import { getScrollContainer } from '@wordpress/dom'; -import { useCallback, useMemo } from '@wordpress/element'; -import { isRTL } from '@wordpress/i18n'; - -const DROP_INDICATOR_WIDTH = 4; - -export default function ListViewDropIndicatorVertical( { - listViewRef, - blockDropTarget, -} ) { - const { rootClientId, clientId, dropPosition } = blockDropTarget || {}; - - const [ rootBlockElement, blockElement ] = 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 ] ); - - // The targetElement is the element that the drop indicator will appear - // before or after. When dropping into an empty block list, blockElement - // is undefined, so the indicator will appear after the rootBlockElement. - const targetElement = blockElement || rootBlockElement; - - const rtl = isRTL(); - - const getDropIndicatorIndent = useCallback( - ( targetElementRect ) => { - if ( ! rootBlockElement ) { - return 0; - } - - // Calculate the indent using the block icon of the root block. - // Using a classname selector here might be flaky and could be - // improved. - const rootBlockIconElement = rootBlockElement.querySelector( - '.block-editor-block-icon' - ); - const rootBlockIconRect = - rootBlockIconElement.getBoundingClientRect(); - return rtl - ? targetElementRect.right - rootBlockIconRect.left - : rootBlockIconRect.right - targetElementRect.left; - }, - [ rootBlockElement, rtl ] - ); - - const popoverAnchor = useMemo( () => { - const isValidDropPosition = - dropPosition === 'top' || - dropPosition === 'bottom' || - dropPosition === 'inside'; - if ( ! targetElement || ! isValidDropPosition ) { - return undefined; - } - - return { - contextElement: targetElement, - getBoundingClientRect() { - const rect = targetElement.getBoundingClientRect(); - const indent = getDropIndicatorIndent( rect ); - // In RTL languages, the drop indicator should be positioned - // to the left of the target element, with the width of the - // indicator determining the indent at the right edge of the - // target element. In LTR languages, the drop indicator should - // end at the right edge of the target element, with the indent - // added to the position of the left edge of the target element. - let left = rtl ? rect.left : rect.left + indent; - let top = 0; - - // In deeply nested lists, where a scrollbar is present, - // the width of the drop indicator should be the width of - // the visible area of the scroll container. Additionally, - // the left edge of the drop indicator line needs to be - // offset by the distance the left edge of the target element - // and the left edge of the scroll container. The ensures - // that the drop indicator position never breaks out of the - // visible area of the scroll container. - const scrollContainer = getScrollContainer( - targetElement, - 'horizontal' - ); - - const doc = targetElement.ownerDocument; - const windowScroll = - scrollContainer === doc.body || - scrollContainer === doc.documentElement; - - // If the scroll container is not the window, offset the left position, if need be. - if ( scrollContainer && ! windowScroll ) { - const scrollContainerRect = - scrollContainer.getBoundingClientRect(); - - // In RTL languages, a vertical scrollbar is present on the - // left edge of the scroll container. The width of the - // scrollbar needs to be accounted for when positioning the - // drop indicator. - const scrollbarWidth = rtl - ? scrollContainer.offsetWidth - - scrollContainer.clientWidth - : 0; - - if ( left < scrollContainerRect.left + scrollbarWidth ) { - left = scrollContainerRect.left + scrollbarWidth; - } - } - - if ( dropPosition === 'top' ) { - top = rect.top; - top -= rect.height * 2; - } else { - // `dropPosition` is either `bottom` or `inside` - top = rect.top; - } - - const height = rect.height; - - return new window.DOMRect( - left, - top, - DROP_INDICATOR_WIDTH, - height - ); - }, - }; - }, [ targetElement, dropPosition, getDropIndicatorIndent, rtl ] ); - - if ( ! targetElement ) { - return null; - } - - return ( - -
- - ); -} diff --git a/packages/block-editor/src/components/list-view/drop-indicator.js b/packages/block-editor/src/components/list-view/drop-indicator.js index 04ce87b4a0c8d4..d46ca03c4bd0e8 100644 --- a/packages/block-editor/src/components/list-view/drop-indicator.js +++ b/packages/block-editor/src/components/list-view/drop-indicator.js @@ -172,7 +172,6 @@ export default function ListViewDropIndicator( { // added to the position of the left edge of the target element. let left = rtl ? rect.left : rect.left + indent; let top = 0; - let bottom = 0; // In deeply nested lists, where a scrollbar is present, // the width of the drop indicator should be the width of @@ -212,16 +211,14 @@ export default function ListViewDropIndicator( { } if ( dropPosition === 'top' ) { - top = rect.top; - bottom = rect.top; + top = rect.top - 4; } else { // `dropPosition` is either `bottom` or `inside` - top = rect.bottom; - bottom = rect.bottom; + top = rect.bottom + rect.height - 4; } const width = getDropIndicatorWidth( rect, indent ); - const height = bottom - top; + const height = 4; return new window.DOMRect( left, top, width, height ); }, diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index 4e18f80dd5a526..707484af25dbb2 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -34,7 +34,6 @@ import { __ } from '@wordpress/i18n'; import ListViewBranch from './branch'; import { ListViewContext } from './context'; import ListViewDropIndicator from './drop-indicator'; -import ListViewDropIndicatorVertical from './drop-indicator-vertical'; import useBlockSelection from './use-block-selection'; import useListViewBlockIndexes from './use-list-view-block-indexes'; import useListViewClientIds from './use-list-view-client-ids'; @@ -75,7 +74,6 @@ export const BLOCK_LIST_ITEM_HEIGHT = 36; * @param {?boolean} props.showBlockMovers Flag to enable block movers. Defaults to `false`. * @param {?boolean} props.isExpanded Flag to determine whether nested levels are expanded by default. Defaults to `false`. * @param {?boolean} props.showAppender Flag to show or hide the block appender. Defaults to `false`. - * @param {?boolean} props.showDropIndicator Flag to show or hide the drop indicator line. Defaults to `false`. * @param {?ComponentType} props.blockSettingsMenu Optional more menu substitution. Defaults to the standard `BlockSettingsDropdown` component. * @param {string} props.rootClientId The client id of the root block from which we determine the blocks to show in the list. * @param {string} props.description Optional accessible description for the tree grid component. @@ -91,7 +89,6 @@ function ListViewComponent( showBlockMovers = false, isExpanded = false, showAppender = false, - showDropIndicator = false, blockSettingsMenu: BlockSettingsMenu = BlockSettingsDropdown, rootClientId, description, @@ -323,17 +320,10 @@ function ListViewComponent( return ( - { showDropIndicator ? ( - - ) : ( - - ) } + { description && ( { description } From 9f455809866d56df42a0f4f0a00a3ccbbf86df47 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 10 Jan 2024 15:06:44 +1100 Subject: [PATCH 19/32] Add reduced motion, fix e2e test --- .../src/components/list-view/style.scss | 6 ++++ .../specs/editor/various/list-view.spec.js | 31 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 52b1e527611704..820f0bbc65b378 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -146,32 +146,38 @@ &.is-displacement-normal { transition: transform 0.2s; transform: translateY(0); + @include reduce-motion("transition"); } &.is-displacement-up { transition: transform 0.2s; transform: translateY(-36px); + @include reduce-motion("transition"); } &.is-displacement-down { transition: transform 0.2s; transform: translateY(36px); + @include reduce-motion("transition"); } // Collapse multi-selections down into a single row space while dragging. &.is-after-dragged-blocks { transition: transform 0.2s; transform: translateY(calc(var(--wp-admin--list-view-dragged-items-height, 36px) * -1)); + @include reduce-motion("transition"); } &.is-after-dragged-blocks.is-displacement-up { transition: transform 0.2s; transform: translateY(calc(-36px + var(--wp-admin--list-view-dragged-items-height, 36px) * -1)); + @include reduce-motion("transition"); } &.is-after-dragged-blocks.is-displacement-down { transition: transform 0.2s; transform: translateY(calc(36px + var(--wp-admin--list-view-dragged-items-height, 36px) * -1)); + @include reduce-motion("transition"); } &.is-dragging { diff --git a/test/e2e/specs/editor/various/list-view.spec.js b/test/e2e/specs/editor/various/list-view.spec.js index 674801cf94abab..00f21b4e51c5ea 100644 --- a/test/e2e/specs/editor/various/list-view.spec.js +++ b/test/e2e/specs/editor/various/list-view.spec.js @@ -53,11 +53,40 @@ test.describe( 'List View', () => { name: 'Paragraph', exact: true, } ); + const imageBlockItem = listView.getByRole( 'gridcell', { + name: 'Image', + exact: true, + } ); const headingBlockItem = listView.getByRole( 'gridcell', { name: 'Heading', exact: true, } ); - await paragraphBlockItem.dragTo( headingBlockItem, { x: 0, y: 0 } ); + + await paragraphBlockItem.hover(); + await page.mouse.down(); + + // To work around a drag and drop bug in Safari, the list view applies + // `pointer-events: none` to the list view while dragging, so that + // `onDragLeave` is not fired when dragging within the list view. + // Without the `force: true` option, the `hover` action will fail + // as playwright will complain that pointer-events are intercepted. + // https://bugs.webkit.org/show_bug.cgi?id=66547 + // See: https://github.com/WordPress/gutenberg/pull/56625 + + // Hover over each block to mimic moving up the list view. + // Also, hover twice to ensure a dragover event is dispatched. + // See: https://playwright.dev/docs/input#dragging-manually + await imageBlockItem.hover( { force: true } ); + await imageBlockItem.hover( { force: true } ); + await headingBlockItem.hover( { force: true } ); + await headingBlockItem.hover( { force: true } ); + + // Disable reason: Need to wait until the throttle timeout of 250ms has passed. + /* eslint-disable playwright/no-wait-for-timeout */ + await editor.page.waitForTimeout( 300 ); + /* eslint-enable playwright/no-wait-for-timeout */ + + await page.mouse.up(); // Ensure the block was dropped correctly. await expect From 7bf8be56a78c10f2ad8a27af05f634cb165af233 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:22:20 +1100 Subject: [PATCH 20/32] Tidy up classname for drag chip in prep for subsequent changes --- .../components/list-view/block-contents.js | 1 + .../src/components/list-view/leaf.js | 2 +- .../src/components/list-view/style.scss | 19 +++++++++++++------ packages/components/src/draggable/index.tsx | 1 - 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block-contents.js b/packages/block-editor/src/components/list-view/block-contents.js index 875d60371f25d3..a3de14e61ef235 100644 --- a/packages/block-editor/src/components/list-view/block-contents.js +++ b/packages/block-editor/src/components/list-view/block-contents.js @@ -81,6 +81,7 @@ const ListViewBlockContents = forwardRef( diff --git a/packages/block-editor/src/components/list-view/leaf.js b/packages/block-editor/src/components/list-view/leaf.js index 56a0f19b373e47..2cb395594a731c 100644 --- a/packages/block-editor/src/components/list-view/leaf.js +++ b/packages/block-editor/src/components/list-view/leaf.js @@ -39,7 +39,7 @@ const ListViewLeaf = forwardRef( enableAnimation: true, triggerAnimationOnChange: path, elementSelector: isDragged - ? '.components-draggable-cloned-element' + ? '.block-editor-list-view-draggable-chip' : undefined, } ); diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 820f0bbc65b378..7d42e1dfcf8a48 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -61,7 +61,7 @@ &.is-selected.is-synced td { background: var(--wp-block-synced-color); } - &.is-synced:not(.is-selected):not([id^="clone-"]) .block-editor-list-view-block-contents { + &.is-synced:not(.is-selected) .block-editor-list-view-block-contents { &:hover, &:focus, .block-editor-block-icon { @@ -97,19 +97,15 @@ } // Border radius for corners of the selected item. - &[id^="clone-"] td:first-child, &.is-first-selected td:first-child { border-top-left-radius: $radius-block-ui; } - &[id^="clone-"] td:last-child, &.is-first-selected td:last-child { border-top-right-radius: $radius-block-ui; } - &[id^="clone-"] td:first-child, &.is-last-selected td:first-child { border-bottom-left-radius: $radius-block-ui; } - &[id^="clone-"] td:last-child, &.is-last-selected td:last-child { border-bottom-right-radius: $radius-block-ui; } @@ -423,8 +419,10 @@ box-shadow: 0 0 0 $radius-block-ui var(--wp-admin-theme-color); } } +} - &[id^="clone-"] { +.block-editor-list-view-draggable-chip { + .block-editor-list-view-leaf { display: flex; // Where possible, restrict the width of the cloned row to the width of the list view. max-width: 338px; @@ -448,6 +446,15 @@ color: inherit; } } + + td:first-child { + border-top-left-radius: $radius-block-ui; + border-bottom-left-radius: $radius-block-ui; + } + td:last-child { + border-top-right-radius: $radius-block-ui; + border-bottom-right-radius: $radius-block-ui; + } } } diff --git a/packages/components/src/draggable/index.tsx b/packages/components/src/draggable/index.tsx index e49fe0792adeed..0a3000538dbf24 100644 --- a/packages/components/src/draggable/index.tsx +++ b/packages/components/src/draggable/index.tsx @@ -160,7 +160,6 @@ export function Draggable( { const clone = element.cloneNode( true ) as HTMLElement; clone.id = `clone-${ elementId }`; - clone.classList.add( 'components-draggable-cloned-element' ); // Position clone right over the original element (20px padding). x = elementLeftOffset - clonePadding; From 8c6699c821feca01a088d056cf8d6e02ca97076d Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:00:00 +1100 Subject: [PATCH 21/32] Remove indent from drop chip, ensure indent is applied to the position --- .../src/components/list-view/leaf.js | 2 +- .../src/components/list-view/style.scss | 32 ++++++++++++++++--- .../list-view/use-list-view-drop-zone.js | 2 +- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/list-view/leaf.js b/packages/block-editor/src/components/list-view/leaf.js index 2cb395594a731c..d9e8a2536301a5 100644 --- a/packages/block-editor/src/components/list-view/leaf.js +++ b/packages/block-editor/src/components/list-view/leaf.js @@ -39,7 +39,7 @@ const ListViewLeaf = forwardRef( enableAnimation: true, triggerAnimationOnChange: path, elementSelector: isDragged - ? '.block-editor-list-view-draggable-chip' + ? '.block-editor-list-view-draggable-chip .block-editor-list-view-leaf' : undefined, } ); diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 7d42e1dfcf8a48..30dfdfe9addba7 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -421,14 +421,39 @@ } } +// First level of indentation is aria-level 2, max indent is 8. +// 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-draggable-chip { .block-editor-list-view-leaf { display: flex; - // Where possible, restrict the width of the cloned row to the width of the list view. - max-width: 338px; height: 36px; opacity: 1; + .block-editor-list-view__expander { + // Remove indent on dragged component. + margin-left: 0 !important; + } + + &[aria-level] { + margin-left: ( $icon-size ) * $block-navigation-max-indent + 4 * ( $block-navigation-max-indent - 1 ); + } + + // When updating the margin for each indentation level, the corresponding + // indentation in `use-list-view-drop-zone.js` must be updated as well + // to ensure the drop zone is aligned with the indentation. + @for $i from 0 to $block-navigation-max-indent { + &[aria-level="#{ $i + 1 }"] { + @if $i - 1 >= 0 { + margin-left: ( $icon-size * $i ) + 4 * ($i - 1) !important; + } + @else { + margin-left: ( $icon-size * $i ) !important; + } + } + } + .block-editor-list-view-block__contents-cell { flex: 1; } @@ -474,9 +499,6 @@ cursor: pointer; } -// First level of indentation is aria-level 2, max indent is 8. -// 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-leaf[aria-level] .block-editor-list-view__expander { margin-left: ( $icon-size ) * $block-navigation-max-indent + 4 * ( $block-navigation-max-indent - 1 ); } diff --git a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js index 71ca979b84dfbc..ab6e9b62bdaa02 100644 --- a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js +++ b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js @@ -533,7 +533,7 @@ export default function useListViewDropZone( { const { ownerDocument } = currentTarget || {}; const dragChipBlockElement = ownerDocument?.querySelector( - '.components-draggable-cloned-element .block-editor-block-icon' + '.block-editor-list-view-draggable-chip .block-editor-block-icon' ); if ( dragChipBlockElement ) { From d882564529ca19f0f2db971de9b22d0da02e0175 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:49:44 +1100 Subject: [PATCH 22/32] Try updating the drag chip to use a border --- .../src/components/list-view/style.scss | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 30dfdfe9addba7..307b4ac8405b19 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -427,15 +427,31 @@ $block-navigation-max-indent: 8; .block-editor-list-view-draggable-chip { .block-editor-list-view-leaf { + // The drag chip uses a transparent background to ensure that the nesting level + // for where a user drops the dragged block is visible. + background-color: rgba(255, 255, 255, 0.5); + border-radius: $radius-block-ui; + box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); display: flex; height: 36px; opacity: 1; + // Reset colors to use the admin color. + td { + background: none !important; + .block-editor-list-view-block-select-button, + .block-editor-block-icon, + .components-button.has-icon { + color: var(--wp-admin-theme-color) !important; + } + } + .block-editor-list-view__expander { - // Remove indent on dragged component. + // Remove indent on the expander, as the dragged component offsets the entire row. margin-left: 0 !important; } + // Apply a margin offset to account for nesting level. &[aria-level] { margin-left: ( $icon-size ) * $block-navigation-max-indent + 4 * ( $block-navigation-max-indent - 1 ); } @@ -462,24 +478,6 @@ $block-navigation-max-indent: 8; display: flex; align-items: center; } - - td { - background-color: rgba(var(--wp-admin-theme-color--rgb), 0.04); - - .block-editor-list-view-block-select-button, - .components-button.has-icon { - color: inherit; - } - } - - td:first-child { - border-top-left-radius: $radius-block-ui; - border-bottom-left-radius: $radius-block-ui; - } - td:last-child { - border-top-right-radius: $radius-block-ui; - border-bottom-right-radius: $radius-block-ui; - } } } From ba2be7480d1182916f8f351f5f418da77ef85ebe Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 11 Jan 2024 16:06:06 +1100 Subject: [PATCH 23/32] Try drop indicator line as preview of final list item --- .../list-view/drop-indicator-preview.js | 294 ++++++++++++++++++ .../src/components/list-view/index.js | 5 +- .../src/components/list-view/style.scss | 11 +- .../sidebar-navigation-item/style.scss | 2 +- 4 files changed, 304 insertions(+), 8 deletions(-) create mode 100644 packages/block-editor/src/components/list-view/drop-indicator-preview.js diff --git a/packages/block-editor/src/components/list-view/drop-indicator-preview.js b/packages/block-editor/src/components/list-view/drop-indicator-preview.js new file mode 100644 index 00000000000000..2a8feb7d2cde16 --- /dev/null +++ b/packages/block-editor/src/components/list-view/drop-indicator-preview.js @@ -0,0 +1,294 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { + __experimentalHStack as HStack, + __experimentalTruncate as Truncate, + Popover, +} from '@wordpress/components'; + +import { getScrollContainer } from '@wordpress/dom'; +import { useCallback, useMemo } from '@wordpress/element'; +import { isRTL } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import BlockIcon from '../block-icon'; +import useBlockDisplayInformation from '../use-block-display-information'; +import useBlockDisplayTitle from '../block-title/use-block-display-title'; +import ListViewExpander from './expander'; + +export default function ListViewDropIndicatorPreview( { + draggedBlockClientId, + listViewRef, + blockDropTarget, +} ) { + const blockInformation = useBlockDisplayInformation( draggedBlockClientId ); + const blockTitle = useBlockDisplayTitle( { + clientId: draggedBlockClientId, + context: 'list-view', + } ); + + const { rootClientId, clientId, dropPosition } = blockDropTarget || {}; + + const [ rootBlockElement, blockElement ] = 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 ] ); + + // The targetElement is the element that the drop indicator will appear + // before or after. When dropping into an empty block list, blockElement + // is undefined, so the indicator will appear after the rootBlockElement. + const targetElement = blockElement || rootBlockElement; + + const rtl = isRTL(); + + const getDropIndicatorWidth = useCallback( + ( targetElementRect, indent ) => { + if ( ! targetElement ) { + return 0; + } + + // Default to assuming that the width of the drop indicator + // should be the same as the target element. + let width = targetElement.offsetWidth; + + // In deeply nested lists, where a scrollbar is present, + // the width of the drop indicator should be the width of + // the scroll container, minus the distance from the left + // edge of the scroll container to the left edge of the + // target element. + const scrollContainer = getScrollContainer( + targetElement, + 'horizontal' + ); + + const ownerDocument = targetElement.ownerDocument; + const windowScroll = + scrollContainer === ownerDocument.body || + scrollContainer === ownerDocument.documentElement; + + if ( scrollContainer && ! windowScroll ) { + const scrollContainerRect = + scrollContainer.getBoundingClientRect(); + + const distanceBetweenContainerAndTarget = isRTL() + ? scrollContainerRect.right - targetElementRect.right + : targetElementRect.left - scrollContainerRect.left; + + const scrollContainerWidth = scrollContainer.clientWidth; + + if ( + scrollContainerWidth < + width + distanceBetweenContainerAndTarget + ) { + width = + scrollContainerWidth - + distanceBetweenContainerAndTarget; + } + + // LTR logic for ensuring the drop indicator does not extend + // beyond the right edge of the scroll container. + if ( + ! rtl && + targetElementRect.left + indent < scrollContainerRect.left + ) { + width -= scrollContainerRect.left - targetElementRect.left; + return width; + } + + // RTL logic for ensuring the drop indicator does not extend + // beyond the right edge of the scroll container. + if ( + rtl && + targetElementRect.right - indent > scrollContainerRect.right + ) { + width -= + targetElementRect.right - scrollContainerRect.right; + return width; + } + } + + // Subtract the indent from the final width of the indicator. + return width - indent; + }, + [ rtl, targetElement ] + ); + + const style = useMemo( () => { + if ( ! targetElement ) { + return {}; + } + + const targetElementRect = targetElement.getBoundingClientRect(); + + return { + width: getDropIndicatorWidth( targetElementRect, 0 ), + }; + }, [ getDropIndicatorWidth, targetElement ] ); + + const ariaLevel = useMemo( () => { + if ( ! rootBlockElement ) { + return 1; + } + + const _ariaLevel = parseInt( + rootBlockElement.getAttribute( 'aria-level' ), + 10 + ); + + return _ariaLevel ? _ariaLevel + 1 : 1; + }, [ rootBlockElement ] ); + + const popoverAnchor = useMemo( () => { + const isValidDropPosition = + dropPosition === 'top' || + dropPosition === 'bottom' || + dropPosition === 'inside'; + if ( ! targetElement || ! isValidDropPosition ) { + return undefined; + } + + return { + contextElement: targetElement, + getBoundingClientRect() { + const rect = targetElement.getBoundingClientRect(); + // In RTL languages, the drop indicator should be positioned + // to the left of the target element, with the width of the + // indicator determining the indent at the right edge of the + // target element. In LTR languages, the drop indicator should + // end at the right edge of the target element, with the indent + // added to the position of the left edge of the target element. + // let left = rtl ? rect.left : rect.left + indent; + let left = rect.left; + let top = 0; + + // In deeply nested lists, where a scrollbar is present, + // the width of the drop indicator should be the width of + // the visible area of the scroll container. Additionally, + // the left edge of the drop indicator line needs to be + // offset by the distance the left edge of the target element + // and the left edge of the scroll container. The ensures + // that the drop indicator position never breaks out of the + // visible area of the scroll container. + const scrollContainer = getScrollContainer( + targetElement, + 'horizontal' + ); + + const doc = targetElement.ownerDocument; + const windowScroll = + scrollContainer === doc.body || + scrollContainer === doc.documentElement; + + // If the scroll container is not the window, offset the left position, if need be. + if ( scrollContainer && ! windowScroll ) { + const scrollContainerRect = + scrollContainer.getBoundingClientRect(); + + // In RTL languages, a vertical scrollbar is present on the + // left edge of the scroll container. The width of the + // scrollbar needs to be accounted for when positioning the + // drop indicator. + const scrollbarWidth = rtl + ? scrollContainer.offsetWidth - + scrollContainer.clientWidth + : 0; + + if ( left < scrollContainerRect.left + scrollbarWidth ) { + left = scrollContainerRect.left + scrollbarWidth; + } + } + + if ( dropPosition === 'top' ) { + top = rect.top - rect.height * 2; + } else { + // `dropPosition` is either `bottom` or `inside` + top = rect.top; + } + + const width = getDropIndicatorWidth( rect, 0 ); + const height = rect.height; + + return new window.DOMRect( left, top, width, height ); + }, + }; + }, [ targetElement, dropPosition, getDropIndicatorWidth, rtl ] ); + + if ( ! targetElement ) { + return null; + } + + return ( + +
+
+
+ {} } /> + + + + + { blockTitle } + + + +
+
+
+
+
+ ); +} diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index 707484af25dbb2..376609ca113017 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -33,7 +33,7 @@ import { __ } from '@wordpress/i18n'; */ import ListViewBranch from './branch'; import { ListViewContext } from './context'; -import ListViewDropIndicator from './drop-indicator'; +import ListViewDropIndicatorPreview from './drop-indicator-preview'; import useBlockSelection from './use-block-selection'; import useListViewBlockIndexes from './use-list-view-block-indexes'; import useListViewClientIds from './use-list-view-client-ids'; @@ -320,7 +320,8 @@ function ListViewComponent( return ( - diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 307b4ac8405b19..9608ba3db38fe5 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -434,6 +434,8 @@ $block-navigation-max-indent: 8; box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); display: flex; height: 36px; + // Where possible, restrict the width of the cloned row to the width of the list view. + max-width: 338px; opacity: 1; // Reset colors to use the admin color. @@ -551,15 +553,14 @@ $block-navigation-max-indent: 8; } } -.block-editor-list-view-drop-indicator--vertical { +.block-editor-list-view-drop-indicator--preview { pointer-events: none; .block-editor-list-view-drop-indicator__line { - background: var(--wp-admin-theme-color); - padding: 4px 0; - height: 28px; - width: 4px; + background: rgba(var(--wp-admin-theme-color--rgb), 0.04); + height: 36px; border-radius: 4px; + overflow: hidden; } } diff --git a/packages/edit-site/src/components/sidebar-navigation-item/style.scss b/packages/edit-site/src/components/sidebar-navigation-item/style.scss index 88ff27a9c1d2f0..908056d52af48c 100644 --- a/packages/edit-site/src/components/sidebar-navigation-item/style.scss +++ b/packages/edit-site/src/components/sidebar-navigation-item/style.scss @@ -33,5 +33,5 @@ .edit-site-sidebar-navigation-screen__content .block-editor-list-view-block-select-button { cursor: grab; - padding: $grid-unit-10; + padding: $grid-unit-10 $grid-unit-10 $grid-unit-10 0; } From 29aa240f962eedad9f2675e0fbb52d8c26230b4e Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 12 Jan 2024 16:51:10 +1100 Subject: [PATCH 24/32] Try offsets for nesting level of drop indicator, and use darker color if adjacent block has selected branch --- .../list-view/drop-indicator-preview.js | 54 ++++++++++++++++++- .../src/components/list-view/style.scss | 4 ++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/list-view/drop-indicator-preview.js b/packages/block-editor/src/components/list-view/drop-indicator-preview.js index 2a8feb7d2cde16..9879221b8c6b6e 100644 --- a/packages/block-editor/src/components/list-view/drop-indicator-preview.js +++ b/packages/block-editor/src/components/list-view/drop-indicator-preview.js @@ -153,6 +153,43 @@ export default function ListViewDropIndicatorPreview( { }; }, [ getDropIndicatorWidth, targetElement ] ); + const horizontalScrollOffsetStyle = useMemo( () => { + if ( ! targetElement ) { + return {}; + } + + const scrollContainer = getScrollContainer( targetElement ); + const ownerDocument = targetElement.ownerDocument; + const windowScroll = + scrollContainer === ownerDocument.body || + scrollContainer === ownerDocument.documentElement; + + if ( scrollContainer && ! windowScroll ) { + const scrollContainerRect = scrollContainer.getBoundingClientRect(); + const targetElementRect = targetElement.getBoundingClientRect(); + + const distanceBetweenContainerAndTarget = rtl + ? scrollContainerRect.right - targetElementRect.right + : targetElementRect.left - scrollContainerRect.left; + + if ( ! rtl && scrollContainerRect.left > targetElementRect.left ) { + return { + transform: `translateX( ${ distanceBetweenContainerAndTarget }px )`, + }; + } + + if ( rtl && scrollContainerRect.right < targetElementRect.right ) { + return { + transform: `translateX( ${ + distanceBetweenContainerAndTarget * -1 + }px )`, + }; + } + } + + return {}; + }, [ rtl, targetElement ] ); + const ariaLevel = useMemo( () => { if ( ! rootBlockElement ) { return 1; @@ -166,6 +203,14 @@ export default function ListViewDropIndicatorPreview( { return _ariaLevel ? _ariaLevel + 1 : 1; }, [ rootBlockElement ] ); + const hasAdjacentSelectedBranch = useMemo( () => { + if ( ! targetElement ) { + return false; + } + + return targetElement.classList.contains( 'is-branch-selected' ); + }, [ targetElement ] ); + const popoverAnchor = useMemo( () => { const isValidDropPosition = dropPosition === 'top' || @@ -255,7 +300,13 @@ export default function ListViewDropIndicatorPreview( { >
{} } /> Date: Mon, 15 Jan 2024 15:43:41 +1100 Subject: [PATCH 25/32] Use drag chip as on trunk, but with opacity of 0.8 --- .../components/list-view/block-contents.js | 10 +--- .../src/components/list-view/leaf.js | 6 +- .../src/components/list-view/style.scss | 56 +------------------ .../list-view/use-list-view-drop-zone.js | 18 +----- .../components/use-moving-animation/index.js | 51 ++++++++--------- 5 files changed, 29 insertions(+), 112 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block-contents.js b/packages/block-editor/src/components/list-view/block-contents.js index a3de14e61ef235..0537a4b48cbe46 100644 --- a/packages/block-editor/src/components/list-view/block-contents.js +++ b/packages/block-editor/src/components/list-view/block-contents.js @@ -47,12 +47,8 @@ const ListViewBlockContents = forwardRef( [] ); - const { - AdditionalBlockContent, - insertedBlock, - listViewInstanceId, - setInsertedBlock, - } = useListViewContext(); + const { AdditionalBlockContent, insertedBlock, setInsertedBlock } = + useListViewContext(); const isBlockMoveTarget = blockMovingClientId && selectedBlockInBlockEditor === clientId; @@ -82,8 +78,6 @@ const ListViewBlockContents = forwardRef( appendToOwnerDocument clientIds={ draggableClientIds } cloneClassname={ 'block-editor-list-view-draggable-chip' } - dragComponent={ null } - elementId={ `list-view-${ listViewInstanceId }-block-${ clientId }` } > { ( { draggable, onDragStart, onDragEnd } ) => ( { const animationRef = useMovingAnimation( { - isSelected, - adjustScrolling: false, + clientId: props[ 'data-block' ], enableAnimation: true, triggerAnimationOnChange: path, - elementSelector: isDragged - ? '.block-editor-list-view-draggable-chip .block-editor-list-view-leaf' - : undefined, } ); const mergedRef = useMergeRefs( [ ref, animationRef ] ); diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index a73863f038c006..347b52069415ee 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -426,61 +426,7 @@ $block-navigation-max-indent: 8; .block-editor-list-view-draggable-chip { - .block-editor-list-view-leaf { - // The drag chip uses a transparent background to ensure that the nesting level - // for where a user drops the dragged block is visible. - background-color: rgba(255, 255, 255, 0.5); - border-radius: $radius-block-ui; - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - display: flex; - height: 36px; - // Where possible, restrict the width of the cloned row to the width of the list view. - max-width: 338px; - opacity: 1; - - // Reset colors to use the admin color. - td { - background: none !important; - .block-editor-list-view-block-select-button, - .block-editor-block-icon, - .components-button.has-icon { - color: var(--wp-admin-theme-color) !important; - } - } - - .block-editor-list-view__expander { - // Remove indent on the expander, as the dragged component offsets the entire row. - margin-left: 0 !important; - } - - // Apply a margin offset to account for nesting level. - &[aria-level] { - margin-left: ( $icon-size ) * $block-navigation-max-indent + 4 * ( $block-navigation-max-indent - 1 ); - } - - // When updating the margin for each indentation level, the corresponding - // indentation in `use-list-view-drop-zone.js` must be updated as well - // to ensure the drop zone is aligned with the indentation. - @for $i from 0 to $block-navigation-max-indent { - &[aria-level="#{ $i + 1 }"] { - @if $i - 1 >= 0 { - margin-left: ( $icon-size * $i ) + 4 * ($i - 1) !important; - } - @else { - margin-left: ( $icon-size * $i ) !important; - } - } - } - - .block-editor-list-view-block__contents-cell { - flex: 1; - } - - .block-editor-list-view-block__menu-cell { - display: flex; - align-items: center; - } - } + opacity: 0.8; } .block-editor-list-view-block__contents-cell, diff --git a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js index ab6e9b62bdaa02..47ccdc8a1d1f88 100644 --- a/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js +++ b/packages/block-editor/src/components/list-view/use-list-view-drop-zone.js @@ -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( @@ -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, diff --git a/packages/block-editor/src/components/use-moving-animation/index.js b/packages/block-editor/src/components/use-moving-animation/index.js index cbc33f047f5e0d..ee1a4610a1d4cf 100644 --- a/packages/block-editor/src/components/use-moving-animation/index.js +++ b/packages/block-editor/src/components/use-moving-animation/index.js @@ -21,6 +21,13 @@ import { store as blockEditorStore } from '../../store'; */ const BLOCK_ANIMATION_THRESHOLD = 200; +function getAbsolutePosition( element ) { + return { + top: element.offsetTop, + left: element.offsetLeft, + }; +} + /** * Hook used to compute the styles required to move a div into a new position. * @@ -32,15 +39,15 @@ const BLOCK_ANIMATION_THRESHOLD = 200; * - It uses the "resetAnimation" flag to reset the animation * from the beginning in order to animate to the new destination point. * - * @param {Object} $1 Options - * @param {*} $1.triggerAnimationOnChange Variable used to trigger the animation if it changes. - * @param {string} $1.elementSelector A CSS selector string used to find the position of an element to animate from. - * @param {string} $1.clientId + * @param {Object} $1 Options + * @param {boolean} $1.enableAnimation Whether to enable the animation. + * @param {*} $1.triggerAnimationOnChange Variable used to trigger the animation if it changes. + * @param {string} $1.clientId */ function useMovingAnimation( { + enableAnimation = true, triggerAnimationOnChange, clientId, - elementSelector, } ) { const ref = useRef(); const { @@ -54,30 +61,17 @@ function useMovingAnimation( { // Whenever the trigger changes, we need to take a snapshot of the current // position of the block to use it as a destination point for the animation. - const { prevRect } = useMemo( - () => { - let previousPosition; - - if ( ref.current && elementSelector ) { - const { ownerDocument } = ref.current; - const element = ownerDocument.querySelector( elementSelector ); - if ( element ) { - previousPosition = element.getBoundingClientRect(); - } - } else if ( ref.current ) { - previousPosition = ref.current.getBoundingClientRect(); - } - - return { - prevRect: previousPosition, - }; - }, + const { previous, prevRect } = useMemo( + () => ( { + previous: ref.current && getAbsolutePosition( ref.current ), + prevRect: ref.current && ref.current.getBoundingClientRect(), + } ), // eslint-disable-next-line react-hooks/exhaustive-deps [ triggerAnimationOnChange ] ); useLayoutEffect( () => { - if ( ! prevRect || ! ref.current ) { + if ( ! previous || ! ref.current ) { return; } @@ -104,6 +98,7 @@ function useMovingAnimation( { // To do: consider enableing the _moving_ animation even for large // posts, while only disabling the _insertion_ animation? const disableAnimation = + ! enableAnimation || window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches || isTyping() || getGlobalBlockCount() > BLOCK_ANIMATION_THRESHOLD; @@ -144,10 +139,10 @@ function useMovingAnimation( { } ); ref.current.style.transform = undefined; - const destination = ref.current.getBoundingClientRect(); + const destination = getAbsolutePosition( ref.current ); - const x = Math.round( prevRect.left - destination.left ); - const y = Math.round( prevRect.top - destination.top ); + const x = Math.round( previous.left - destination.left ); + const y = Math.round( previous.top - destination.top ); controller.start( { x: 0, y: 0, from: { x, y } } ); @@ -155,8 +150,10 @@ function useMovingAnimation( { controller.stop(); }; }, [ + previous, prevRect, clientId, + enableAnimation, isTyping, getGlobalBlockCount, isBlockSelected, From 66ac213028d46f6ca9ce448bfb8421d465091af6 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:56:16 +1100 Subject: [PATCH 26/32] Consolidate drop indicator logic --- .../list-view/drop-indicator-preview.js | 346 ------------------ .../components/list-view/drop-indicator.js | 180 ++++++--- .../src/components/list-view/index.js | 2 +- 3 files changed, 138 insertions(+), 390 deletions(-) delete mode 100644 packages/block-editor/src/components/list-view/drop-indicator-preview.js diff --git a/packages/block-editor/src/components/list-view/drop-indicator-preview.js b/packages/block-editor/src/components/list-view/drop-indicator-preview.js deleted file mode 100644 index 9879221b8c6b6e..00000000000000 --- a/packages/block-editor/src/components/list-view/drop-indicator-preview.js +++ /dev/null @@ -1,346 +0,0 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - -/** - * WordPress dependencies - */ -import { - __experimentalHStack as HStack, - __experimentalTruncate as Truncate, - Popover, -} from '@wordpress/components'; - -import { getScrollContainer } from '@wordpress/dom'; -import { useCallback, useMemo } from '@wordpress/element'; -import { isRTL } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import BlockIcon from '../block-icon'; -import useBlockDisplayInformation from '../use-block-display-information'; -import useBlockDisplayTitle from '../block-title/use-block-display-title'; -import ListViewExpander from './expander'; - -export default function ListViewDropIndicatorPreview( { - draggedBlockClientId, - listViewRef, - blockDropTarget, -} ) { - const blockInformation = useBlockDisplayInformation( draggedBlockClientId ); - const blockTitle = useBlockDisplayTitle( { - clientId: draggedBlockClientId, - context: 'list-view', - } ); - - const { rootClientId, clientId, dropPosition } = blockDropTarget || {}; - - const [ rootBlockElement, blockElement ] = 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 ] ); - - // The targetElement is the element that the drop indicator will appear - // before or after. When dropping into an empty block list, blockElement - // is undefined, so the indicator will appear after the rootBlockElement. - const targetElement = blockElement || rootBlockElement; - - const rtl = isRTL(); - - const getDropIndicatorWidth = useCallback( - ( targetElementRect, indent ) => { - if ( ! targetElement ) { - return 0; - } - - // Default to assuming that the width of the drop indicator - // should be the same as the target element. - let width = targetElement.offsetWidth; - - // In deeply nested lists, where a scrollbar is present, - // the width of the drop indicator should be the width of - // the scroll container, minus the distance from the left - // edge of the scroll container to the left edge of the - // target element. - const scrollContainer = getScrollContainer( - targetElement, - 'horizontal' - ); - - const ownerDocument = targetElement.ownerDocument; - const windowScroll = - scrollContainer === ownerDocument.body || - scrollContainer === ownerDocument.documentElement; - - if ( scrollContainer && ! windowScroll ) { - const scrollContainerRect = - scrollContainer.getBoundingClientRect(); - - const distanceBetweenContainerAndTarget = isRTL() - ? scrollContainerRect.right - targetElementRect.right - : targetElementRect.left - scrollContainerRect.left; - - const scrollContainerWidth = scrollContainer.clientWidth; - - if ( - scrollContainerWidth < - width + distanceBetweenContainerAndTarget - ) { - width = - scrollContainerWidth - - distanceBetweenContainerAndTarget; - } - - // LTR logic for ensuring the drop indicator does not extend - // beyond the right edge of the scroll container. - if ( - ! rtl && - targetElementRect.left + indent < scrollContainerRect.left - ) { - width -= scrollContainerRect.left - targetElementRect.left; - return width; - } - - // RTL logic for ensuring the drop indicator does not extend - // beyond the right edge of the scroll container. - if ( - rtl && - targetElementRect.right - indent > scrollContainerRect.right - ) { - width -= - targetElementRect.right - scrollContainerRect.right; - return width; - } - } - - // Subtract the indent from the final width of the indicator. - return width - indent; - }, - [ rtl, targetElement ] - ); - - const style = useMemo( () => { - if ( ! targetElement ) { - return {}; - } - - const targetElementRect = targetElement.getBoundingClientRect(); - - return { - width: getDropIndicatorWidth( targetElementRect, 0 ), - }; - }, [ getDropIndicatorWidth, targetElement ] ); - - const horizontalScrollOffsetStyle = useMemo( () => { - if ( ! targetElement ) { - return {}; - } - - const scrollContainer = getScrollContainer( targetElement ); - const ownerDocument = targetElement.ownerDocument; - const windowScroll = - scrollContainer === ownerDocument.body || - scrollContainer === ownerDocument.documentElement; - - if ( scrollContainer && ! windowScroll ) { - const scrollContainerRect = scrollContainer.getBoundingClientRect(); - const targetElementRect = targetElement.getBoundingClientRect(); - - const distanceBetweenContainerAndTarget = rtl - ? scrollContainerRect.right - targetElementRect.right - : targetElementRect.left - scrollContainerRect.left; - - if ( ! rtl && scrollContainerRect.left > targetElementRect.left ) { - return { - transform: `translateX( ${ distanceBetweenContainerAndTarget }px )`, - }; - } - - if ( rtl && scrollContainerRect.right < targetElementRect.right ) { - return { - transform: `translateX( ${ - distanceBetweenContainerAndTarget * -1 - }px )`, - }; - } - } - - return {}; - }, [ rtl, targetElement ] ); - - const ariaLevel = useMemo( () => { - if ( ! rootBlockElement ) { - return 1; - } - - const _ariaLevel = parseInt( - rootBlockElement.getAttribute( 'aria-level' ), - 10 - ); - - return _ariaLevel ? _ariaLevel + 1 : 1; - }, [ rootBlockElement ] ); - - const hasAdjacentSelectedBranch = useMemo( () => { - if ( ! targetElement ) { - return false; - } - - return targetElement.classList.contains( 'is-branch-selected' ); - }, [ targetElement ] ); - - const popoverAnchor = useMemo( () => { - const isValidDropPosition = - dropPosition === 'top' || - dropPosition === 'bottom' || - dropPosition === 'inside'; - if ( ! targetElement || ! isValidDropPosition ) { - return undefined; - } - - return { - contextElement: targetElement, - getBoundingClientRect() { - const rect = targetElement.getBoundingClientRect(); - // In RTL languages, the drop indicator should be positioned - // to the left of the target element, with the width of the - // indicator determining the indent at the right edge of the - // target element. In LTR languages, the drop indicator should - // end at the right edge of the target element, with the indent - // added to the position of the left edge of the target element. - // let left = rtl ? rect.left : rect.left + indent; - let left = rect.left; - let top = 0; - - // In deeply nested lists, where a scrollbar is present, - // the width of the drop indicator should be the width of - // the visible area of the scroll container. Additionally, - // the left edge of the drop indicator line needs to be - // offset by the distance the left edge of the target element - // and the left edge of the scroll container. The ensures - // that the drop indicator position never breaks out of the - // visible area of the scroll container. - const scrollContainer = getScrollContainer( - targetElement, - 'horizontal' - ); - - const doc = targetElement.ownerDocument; - const windowScroll = - scrollContainer === doc.body || - scrollContainer === doc.documentElement; - - // If the scroll container is not the window, offset the left position, if need be. - if ( scrollContainer && ! windowScroll ) { - const scrollContainerRect = - scrollContainer.getBoundingClientRect(); - - // In RTL languages, a vertical scrollbar is present on the - // left edge of the scroll container. The width of the - // scrollbar needs to be accounted for when positioning the - // drop indicator. - const scrollbarWidth = rtl - ? scrollContainer.offsetWidth - - scrollContainer.clientWidth - : 0; - - if ( left < scrollContainerRect.left + scrollbarWidth ) { - left = scrollContainerRect.left + scrollbarWidth; - } - } - - if ( dropPosition === 'top' ) { - top = rect.top - rect.height * 2; - } else { - // `dropPosition` is either `bottom` or `inside` - top = rect.top; - } - - const width = getDropIndicatorWidth( rect, 0 ); - const height = rect.height; - - return new window.DOMRect( left, top, width, height ); - }, - }; - }, [ targetElement, dropPosition, getDropIndicatorWidth, rtl ] ); - - if ( ! targetElement ) { - return null; - } - - return ( - -
-
-
- {} } /> - - - - - { blockTitle } - - - -
-
-
-
-
- ); -} diff --git a/packages/block-editor/src/components/list-view/drop-indicator.js b/packages/block-editor/src/components/list-view/drop-indicator.js index d46ca03c4bd0e8..9879221b8c6b6e 100644 --- a/packages/block-editor/src/components/list-view/drop-indicator.js +++ b/packages/block-editor/src/components/list-view/drop-indicator.js @@ -1,15 +1,40 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ -import { Popover } from '@wordpress/components'; +import { + __experimentalHStack as HStack, + __experimentalTruncate as Truncate, + Popover, +} from '@wordpress/components'; + import { getScrollContainer } from '@wordpress/dom'; import { useCallback, useMemo } from '@wordpress/element'; import { isRTL } from '@wordpress/i18n'; -export default function ListViewDropIndicator( { +/** + * Internal dependencies + */ +import BlockIcon from '../block-icon'; +import useBlockDisplayInformation from '../use-block-display-information'; +import useBlockDisplayTitle from '../block-title/use-block-display-title'; +import ListViewExpander from './expander'; + +export default function ListViewDropIndicatorPreview( { + draggedBlockClientId, listViewRef, blockDropTarget, } ) { + const blockInformation = useBlockDisplayInformation( draggedBlockClientId ); + const blockTitle = useBlockDisplayTitle( { + clientId: draggedBlockClientId, + context: 'list-view', + } ); + const { rootClientId, clientId, dropPosition } = blockDropTarget || {}; const [ rootBlockElement, blockElement ] = useMemo( () => { @@ -35,7 +60,7 @@ export default function ListViewDropIndicator( { : undefined; return [ _rootBlockElement, _blockElement ]; - }, [ rootClientId, clientId ] ); + }, [ listViewRef, rootClientId, clientId ] ); // The targetElement is the element that the drop indicator will appear // before or after. When dropping into an empty block list, blockElement @@ -44,27 +69,6 @@ export default function ListViewDropIndicator( { const rtl = isRTL(); - const getDropIndicatorIndent = useCallback( - ( targetElementRect ) => { - if ( ! rootBlockElement ) { - return 0; - } - - // Calculate the indent using the block icon of the root block. - // Using a classname selector here might be flaky and could be - // improved. - const rootBlockIconElement = rootBlockElement.querySelector( - '.block-editor-block-icon' - ); - const rootBlockIconRect = - rootBlockIconElement.getBoundingClientRect(); - return rtl - ? targetElementRect.right - rootBlockIconRect.left - : rootBlockIconRect.right - targetElementRect.left; - }, - [ rootBlockElement, rtl ] - ); - const getDropIndicatorWidth = useCallback( ( targetElementRect, indent ) => { if ( ! targetElement ) { @@ -143,12 +147,69 @@ export default function ListViewDropIndicator( { } const targetElementRect = targetElement.getBoundingClientRect(); - const indent = getDropIndicatorIndent( targetElementRect ); return { - width: getDropIndicatorWidth( targetElementRect, indent ), + width: getDropIndicatorWidth( targetElementRect, 0 ), }; - }, [ getDropIndicatorIndent, getDropIndicatorWidth, targetElement ] ); + }, [ getDropIndicatorWidth, targetElement ] ); + + const horizontalScrollOffsetStyle = useMemo( () => { + if ( ! targetElement ) { + return {}; + } + + const scrollContainer = getScrollContainer( targetElement ); + const ownerDocument = targetElement.ownerDocument; + const windowScroll = + scrollContainer === ownerDocument.body || + scrollContainer === ownerDocument.documentElement; + + if ( scrollContainer && ! windowScroll ) { + const scrollContainerRect = scrollContainer.getBoundingClientRect(); + const targetElementRect = targetElement.getBoundingClientRect(); + + const distanceBetweenContainerAndTarget = rtl + ? scrollContainerRect.right - targetElementRect.right + : targetElementRect.left - scrollContainerRect.left; + + if ( ! rtl && scrollContainerRect.left > targetElementRect.left ) { + return { + transform: `translateX( ${ distanceBetweenContainerAndTarget }px )`, + }; + } + + if ( rtl && scrollContainerRect.right < targetElementRect.right ) { + return { + transform: `translateX( ${ + distanceBetweenContainerAndTarget * -1 + }px )`, + }; + } + } + + return {}; + }, [ rtl, targetElement ] ); + + const ariaLevel = useMemo( () => { + if ( ! rootBlockElement ) { + return 1; + } + + const _ariaLevel = parseInt( + rootBlockElement.getAttribute( 'aria-level' ), + 10 + ); + + return _ariaLevel ? _ariaLevel + 1 : 1; + }, [ rootBlockElement ] ); + + const hasAdjacentSelectedBranch = useMemo( () => { + if ( ! targetElement ) { + return false; + } + + return targetElement.classList.contains( 'is-branch-selected' ); + }, [ targetElement ] ); const popoverAnchor = useMemo( () => { const isValidDropPosition = @@ -163,14 +224,14 @@ export default function ListViewDropIndicator( { contextElement: targetElement, getBoundingClientRect() { const rect = targetElement.getBoundingClientRect(); - const indent = getDropIndicatorIndent( rect ); // In RTL languages, the drop indicator should be positioned // to the left of the target element, with the width of the // indicator determining the indent at the right edge of the // target element. In LTR languages, the drop indicator should // end at the right edge of the target element, with the indent // added to the position of the left edge of the target element. - let left = rtl ? rect.left : rect.left + indent; + // let left = rtl ? rect.left : rect.left + indent; + let left = rect.left; let top = 0; // In deeply nested lists, where a scrollbar is present, @@ -211,25 +272,19 @@ export default function ListViewDropIndicator( { } if ( dropPosition === 'top' ) { - top = rect.top - 4; + top = rect.top - rect.height * 2; } else { // `dropPosition` is either `bottom` or `inside` - top = rect.bottom + rect.height - 4; + top = rect.top; } - const width = getDropIndicatorWidth( rect, indent ); - const height = 4; + const width = getDropIndicatorWidth( rect, 0 ); + const height = rect.height; return new window.DOMRect( left, top, width, height ); }, }; - }, [ - targetElement, - dropPosition, - getDropIndicatorIndent, - getDropIndicatorWidth, - rtl, - ] ); + }, [ targetElement, dropPosition, getDropIndicatorWidth, rtl ] ); if ( ! targetElement ) { return null; @@ -240,13 +295,52 @@ export default function ListViewDropIndicator( { animate={ false } anchor={ popoverAnchor } focusOnMount={ false } - className="block-editor-list-view-drop-indicator" + className="block-editor-list-view-drop-indicator--preview" variant="unstyled" >
+ className={ classnames( + 'block-editor-list-view-drop-indicator__line', + { + 'block-editor-list-view-drop-indicator__line--darker': + hasAdjacentSelectedBranch, + } + ) } + > +
+
+ {} } /> + + + + + { blockTitle } + + + +
+
+
+
); } diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index 376609ca113017..3506257b793c5d 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -33,7 +33,7 @@ import { __ } from '@wordpress/i18n'; */ import ListViewBranch from './branch'; import { ListViewContext } from './context'; -import ListViewDropIndicatorPreview from './drop-indicator-preview'; +import ListViewDropIndicatorPreview from './drop-indicator'; import useBlockSelection from './use-block-selection'; import useListViewBlockIndexes from './use-list-view-block-indexes'; import useListViewClientIds from './use-list-view-client-ids'; From 3839470dfe7259fb1ade10c3da98f5b679f07cab Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 16 Jan 2024 15:21:19 +1100 Subject: [PATCH 27/32] Fix drop indicator when dragging files --- .../src/components/list-view/branch.js | 38 +++++++++++++++---- .../components/list-view/drop-indicator.js | 2 + .../src/components/list-view/style.scss | 6 +++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/list-view/branch.js b/packages/block-editor/src/components/list-view/branch.js index 4d10bcd3d2370f..9471fcd1e880d1 100644 --- a/packages/block-editor/src/components/list-view/branch.js +++ b/packages/block-editor/src/components/list-view/branch.js @@ -155,17 +155,22 @@ function ListViewBranch( props ) { let isNesting; let isAfterDraggedBlocks; - if ( firstDraggedBlockIndex !== undefined && ! isDragged ) { + if ( ! isDragged ) { const thisBlockIndex = blockIndexes[ clientId ]; isAfterDraggedBlocks = thisBlockIndex > firstDraggedBlockIndex; + isNesting = + typeof blockDropTargetIndex === 'number' && + blockDropTargetIndex - 1 === thisBlockIndex && + blockDropPosition === 'inside'; // Determine where to displace the position of the current block, relative // to the blocks being dragged (in their original position) and the drop target // (the position where a user is currently dragging the blocks to). if ( blockDropTargetIndex !== undefined && - blockDropTargetIndex !== null + blockDropTargetIndex !== null && + firstDraggedBlockIndex !== undefined ) { // If the block is being dragged and there is a valid drop target, // determine if the block being rendered should be displaced up or down. @@ -191,11 +196,10 @@ function ListViewBranch( props ) { displacement = 'normal'; } } - - isNesting = - blockDropTargetIndex - 1 === thisBlockIndex && - blockDropPosition === 'inside'; - } else if ( blockDropTargetIndex === null ) { + } else if ( + blockDropTargetIndex === null && + firstDraggedBlockIndex !== undefined + ) { // A `null` value for `blockDropTargetIndex` indicates that the // drop target is outside of the valid areas within the list view. // In this case, the drag is still active, but as there is no @@ -209,6 +213,26 @@ function ListViewBranch( props ) { } else { displacement = 'normal'; } + } else if ( + blockDropTargetIndex !== undefined && + blockDropTargetIndex !== null && + firstDraggedBlockIndex === undefined + ) { + // If the blockdrop target is defined, but there are no dragged blocks, + // then the block should be displaced relative to the drop target. + if ( + thisBlockIndex !== undefined + // blockDropTargetIndex !== 0 && + // blockDropPosition !== 'top' + ) { + if ( thisBlockIndex < blockDropTargetIndex ) { + displacement = 'normal'; + } else { + displacement = 'down'; + } + } + } else if ( blockDropTargetIndex === null ) { + displacement = 'normal'; } } diff --git a/packages/block-editor/src/components/list-view/drop-indicator.js b/packages/block-editor/src/components/list-view/drop-indicator.js index 9879221b8c6b6e..ed989222022935 100644 --- a/packages/block-editor/src/components/list-view/drop-indicator.js +++ b/packages/block-editor/src/components/list-view/drop-indicator.js @@ -297,6 +297,8 @@ export default function ListViewDropIndicatorPreview( { focusOnMount={ false } className="block-editor-list-view-drop-indicator--preview" variant="unstyled" + flip={ false } + resize={ true } >