Skip to content

Commit

Permalink
Support drag-and-drop for submenus of navigation blocks (#24479)
Browse files Browse the repository at this point in the history
* Support drag and drop for submenus in Navigation block

* Add a bunch of CSS to handle dragging style properly

* Extract useIsDraggingWithin into its own hook

* Add jsdoc
  • Loading branch information
kevin940726 authored Aug 20, 2020
1 parent 335219f commit 748c632
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@

// This hides the block being dragged.
// The effect is that the block being dragged appears to be "lifted".
.is-dragging.is-selected,
.is-dragging.is-multi-selected {
.is-dragging {
display: none !important;
}
6 changes: 4 additions & 2 deletions packages/block-editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ function BlockListBlock( {
{
'wp-block': ! isAligned,
'has-warning': ! isValid || !! hasError || isUnregisteredBlock,
'is-selected': isSelected,
// Don't select the item while dragging.
'is-selected': isSelected && ! isDragging,
'is-highlighted': isHighlighted,
'is-multi-selected': isMultiSelected,
'is-reusable': isReusableBlock( blockType ),
Expand All @@ -160,7 +161,8 @@ function BlockListBlock( {
'is-focused':
isFocusMode && ( isSelected || isAncestorOfSelectedBlock ),
'is-focus-mode': isFocusMode,
'has-child-selected': isAncestorOfSelectedBlock,
// Don't select child while dragging.
'has-child-selected': isAncestorOfSelectedBlock && ! isDragging,
},
className
);
Expand Down
11 changes: 11 additions & 0 deletions packages/block-editor/src/components/block-list/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,15 @@
}
}

// Hide the popover block editor list while dragging.
// Using a hacky animation to delay hiding the element so that dragging can still work.
@keyframes hide-during-dragging {
to {
position: fixed;
transform: translate(9999px, 9999px);
}
}

.components-popover.block-editor-block-list__block-popover {
z-index: z-index(".block-editor-block-list__block-popover");
position: absolute;
Expand Down Expand Up @@ -657,6 +666,8 @@

.is-dragging-components-draggable & {
opacity: 0;
// Use a minimal duration to delay hiding the element, see hide-during-dragging animation for more details.
animation: hide-during-dragging 1ms linear forwards;
}
}

Expand Down
81 changes: 76 additions & 5 deletions packages/block-library/src/navigation-link/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import { escape, get, head, find } from 'lodash';
*/
import { compose } from '@wordpress/compose';
import { createBlock } from '@wordpress/blocks';
import { useDispatch, withDispatch, withSelect } from '@wordpress/data';
import {
useSelect,
useDispatch,
withDispatch,
withSelect,
} from '@wordpress/data';
import {
KeyboardShortcuts,
PanelBody,
Expand Down Expand Up @@ -39,6 +44,54 @@ import { link as linkIcon } from '@wordpress/icons';
*/
import { ToolbarSubmenuIcon, ItemSubmenuIcon } from './icons';

/**
* A React hook to determine if it's dragging within the target element.
*
* @typedef {import('@wordpress/element').RefObject} RefObject
*
* @param {RefObject<HTMLElement>} elementRef The target elementRef object.
*
* @return {boolean} Is dragging within the target element.
*/
const useIsDraggingWithin = ( elementRef ) => {
const [ isDraggingWithin, setIsDraggingWithin ] = useState( false );

useEffect( () => {
function handleDragStart( event ) {
// Check the first time when the dragging starts.
handleDragEnter( event );
}

// Set to false whenever the user cancel the drag event by either releasing the mouse or press Escape.
function handleDragEnd() {
setIsDraggingWithin( false );
}

function handleDragEnter( event ) {
// Check if the current target is inside the item element.
if ( elementRef.current.contains( event.target ) ) {
setIsDraggingWithin( true );
} else {
setIsDraggingWithin( false );
}
}

// Bind these events to the document to catch all drag events.
// Ideally, we can also use `event.relatedTarget`, but sadly that doesn't work in Safari.
document.addEventListener( 'dragstart', handleDragStart );
document.addEventListener( 'dragend', handleDragEnd );
document.addEventListener( 'dragenter', handleDragEnter );

return () => {
document.removeEventListener( 'dragstart', handleDragStart );
document.removeEventListener( 'dragend', handleDragEnd );
document.removeEventListener( 'dragenter', handleDragEnter );
};
}, [] );

return isDraggingWithin;
};

function NavigationLinkEdit( {
attributes,
hasDescendants,
Expand All @@ -65,9 +118,16 @@ function NavigationLinkEdit( {
};
const { saveEntityRecord } = useDispatch( 'core' );
const [ isLinkOpen, setIsLinkOpen ] = useState( false );
const listItemRef = useRef( null );
const isDraggingWithin = useIsDraggingWithin( listItemRef );
const itemLabelPlaceholder = __( 'Add link…' );
const ref = useRef();

const isDraggingBlocks = useSelect(
( select ) => select( 'core/block-editor' ).isDraggingBlocks(),
[]
);

// Show the LinkControl on mount if the URL is empty
// ( When adding a new menu item)
// This can't be done in the useState call because it cconflicts
Expand Down Expand Up @@ -183,8 +243,13 @@ function NavigationLinkEdit( {
</InspectorControls>
<Block.li
className={ classnames( {
'is-editing': isSelected || isParentOfSelectedBlock,
'is-selected': isSelected,
'is-editing':
( isSelected || isParentOfSelectedBlock ) &&
// Don't show the element as editing while dragging.
! isDraggingBlocks,
// Don't select the element while dragging.
'is-selected': isSelected && ! isDraggingBlocks,
'is-dragging-within': isDraggingWithin,
'has-link': !! url,
'has-child': hasDescendants,
'has-text-color': rgbTextColor,
Expand All @@ -196,6 +261,7 @@ function NavigationLinkEdit( {
color: rgbTextColor,
backgroundColor: rgbBackgroundColor,
} }
ref={ listItemRef }
>
<div className="wp-block-navigation-link__content">
<RichText
Expand Down Expand Up @@ -282,7 +348,9 @@ function NavigationLinkEdit( {
renderAppender={
( isSelected && hasDescendants ) ||
( isImmediateParentOfSelectedBlock &&
! selectedBlockHasDescendants )
! selectedBlockHasDescendants ) ||
// Show the appender while dragging to allow inserting element between item and the appender.
( isDraggingBlocks && hasDescendants )
? InnerBlocks.DefaultAppender
: false
}
Expand All @@ -292,7 +360,10 @@ function NavigationLinkEdit( {
className: classnames(
'wp-block-navigation__container',
{
'is-parent-of-selected-block': isParentOfSelectedBlock,
'is-parent-of-selected-block':
isParentOfSelectedBlock &&
// Don't select as parent of selected block while dragging.
! isDraggingBlocks,
}
),
} }
Expand Down
20 changes: 15 additions & 5 deletions packages/block-library/src/navigation/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,27 @@ $navigation-item-height: 46px;
}

&.is-selected,
&.has-child-selected {
&.has-child-selected,
// Show the submenu list when is dragging and drag enter the list item.
body.is-dragging-components-draggable &.is-dragging-within {
> .wp-block-navigation__container {
opacity: 1;
visibility: visible;
}
}
}

body.is-dragging-components-draggable .wp-block-navigation-link > .wp-block-navigation__container {
// Set opacity to 1 to still be able to show the draggable chip.
opacity: 1;
visibility: hidden;

// Show the chip but hide the submenu list.
.block-editor-block-draggable-chip-wrapper {
visibility: visible;
}
}

/**
* Colors Selector component
*/
Expand Down Expand Up @@ -95,7 +108,7 @@ $colors-selector-size: 22px;
min-width: $colors-selector-size;
height: $colors-selector-size;
min-height: $colors-selector-size;
line-height: ($colors-selector-size - 2);
line-height: ( $colors-selector-size - 2 );
padding: 2px;

> svg {
Expand Down Expand Up @@ -145,7 +158,6 @@ $color-control-label-height: 20px;
}

.wp-block-navigation-placeholder {

.components-spinner {
margin-top: -4px;
margin-left: 4px;
Expand All @@ -165,7 +177,6 @@ $color-control-label-height: 20px;

// Styles for when there are Menus present in the dropdown.
.components-custom-select-control.has-menus .components-custom-select-control__item {

// Creates a faux divider between the menu options if present
&.is-create-empty-option {
position: relative; // positioning context for pseudo
Expand Down Expand Up @@ -195,7 +206,6 @@ $color-control-label-height: 20px;
font-family: $default-font;
font-size: $default-font-size;
}

}

.wp-block-navigation .block-editor-button-block-appender {
Expand Down

0 comments on commit 748c632

Please sign in to comment.