Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disallow drag from Nav list view into block canvas #47615

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/reference-guides/data/data-core-block-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -1573,6 +1573,7 @@ Returns an action object used in signalling that the user has begun to drag bloc
_Parameters_

- _clientIds_ `string[]`: An array of client ids being dragged
- _targets_ `Array`: the names of target dropzones into which this draggable can be dropped.

_Returns_

Expand Down
71 changes: 71 additions & 0 deletions packages/block-editor/src/components/block-draggable/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Block Draggable

Block Draggable is a block-specific implementation of a `<Draggable>` which defines behaviour for dragged elements in a block editor context, including (but not limited to):

- determining whether the given block(s) can be moved.
- setting information on the [`transferData` object](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer).
- (optionally) setting the `target` dropzones with which this draggable is compatible.
- displaying a suitable "chip" during the drag operation.
- managing scrolling during the drag.

Note that the majority of the behaviour is delegated to `Draggable` from `@wordpress/components`.

## Usage

```js
import { BlockDraggable } from '@wordpress/block-editor';

function MyComponent() {
return <BlockDraggable clientIds={ clientIds } />;
}
```

## Props

### clientIds

- Type: `Array`
- Required: Yes

Blocks IDs of candidates to be dragged.

### targets

- Type: `Array[string]`
- Required: No

A list of dropzone names that this draggable considers valid drop targets. If provided then it will only be possible to drop the draggable in a _named_ dropzone that is included in the list.

### `children`

- Type: Function
- Required: No

Component children as a function. The function receives the following arguments:

- `draggable` - whether or not the block(s) are deemed to be draggable.
- `onDragStart` (optional) - an event handler to be passed as the `onDragStart` event prop of any child node.
- `onDragEnd` - an event handler to be passed as the `onDragEnd` event prop of any child node.

See [`Draggable`](./packages/components/src/draggable/README.md) for more information.

### cloneClassname

- Type: `string`
- Required: No

A className to be passed to the clone of the draggable.

### `onDragStart`

- Type: Function
- Required: No

A function to be called on the `ondragstart` event.

### `onDragEnd`

- Type: Function
- Required: No

A function to be called on the `ondragend` event.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const BlockDraggable = ( {
cloneClassname,
onDragStart,
onDragEnd,
targets = [],
} ) => {
const { srcRootClientId, isDraggable, icon } = useSelect(
( select ) => {
Expand Down Expand Up @@ -59,6 +60,7 @@ const BlockDraggable = ( {
type: 'block',
srcClientIds: clientIds,
srcRootClientId,
targets,
};

return (
Expand All @@ -67,7 +69,7 @@ const BlockDraggable = ( {
__experimentalTransferDataType="wp-blocks"
transferData={ transferData }
onDragStart={ ( event ) => {
startDraggingBlocks( clientIds );
startDraggingBlocks( clientIds, targets );
isDragging.current = true;

startScrolling( event );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from '../../utils/math';
import useOnBlockDrop from '../use-on-block-drop';
import { store as blockEditorStore } from '../../store';
import { unlock } from '../../experiments';

/** @typedef {import('../../utils/math').WPPoint} WPPoint */

Expand Down Expand Up @@ -179,26 +180,39 @@ function getListViewDropTarget( blocksData, position ) {
/**
* A react hook for implementing a drop zone in list view.
*
* @param {string} dropZoneName the name of the drop zone.
* @return {WPListViewDropZoneTarget} The drop target.
*/
export default function useListViewDropZone() {
export default function useListViewDropZone( dropZoneName ) {
const {
getBlockRootClientId,
getBlockIndex,
getBlockCount,
getDraggedBlockClientIds,
canInsertBlocks,
} = useSelect( blockEditorStore );
getDraggedBlocksTargets,
} = unlock( useSelect( blockEditorStore ) );
const [ target, setTarget ] = useState();
const { rootClientId: targetRootClientId, blockIndex: targetBlockIndex } =
Copy link
Contributor

Choose a reason for hiding this comment

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

it's a bit confusing the difference between the target variable this hook returns and the target finding done later.

target || {};

const onBlockDrop = useOnBlockDrop( targetRootClientId, targetBlockIndex );
const onBlockDrop = useOnBlockDrop( targetRootClientId, targetBlockIndex, {
dropZoneName,
} );

const draggedBlockClientIds = getDraggedBlockClientIds();
const throttled = useThrottle(
useCallback(
( event, currentTarget ) => {
const draggedBlocksTargets = getDraggedBlocksTargets();

if (
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't this check be in useDropZone so it applies to all drop zones?

draggedBlocksTargets?.length &&
! draggedBlocksTargets.includes( dropZoneName )
) {
// If drag targets are defined and the drop zone doesn't match, don't allow dropping.
return;
}
const position = { x: event.clientX, y: event.clientY };
const isBlockDrag = !! draggedBlockClientIds?.length;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,10 @@ const ListViewBlockContents = forwardRef(
} }
/>
) }
<BlockDraggable clientIds={ draggableClientIds }>
<BlockDraggable
clientIds={ draggableClientIds }
targets={ [ 'offcanvas' ] }
>
{ ( { draggable, onDragStart, onDragEnd } ) => (
<ListViewBlockSelectButton
ref={ ref }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ function OffCanvasEditor(

const [ expandedState, setExpandedState ] = useReducer( expanded, {} );

const { ref: dropZoneRef, target: blockDropTarget } = useListViewDropZone();
const { ref: dropZoneRef, target: blockDropTarget } =
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make offcanvas a prop that we pass to the component, so that when we combine this with the List View we already have the tools we need?

useListViewDropZone( 'offcanvas' );
const elementRef = useRef();
const treeGridRef = useMergeRefs( [ elementRef, dropZoneRef, ref ] );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from '../../utils/math';
import useOnBlockDrop from '../use-on-block-drop';
import { store as blockEditorStore } from '../../store';
import { unlock } from '../../experiments';

/** @typedef {import('../../utils/math').WPPoint} WPPoint */

Expand Down Expand Up @@ -179,26 +180,39 @@ function getListViewDropTarget( blocksData, position ) {
/**
* A react hook for implementing a drop zone in list view.
*
* @param {string} dropZoneName The name of the drop zone.
* @return {WPListViewDropZoneTarget} The drop target.
*/
export default function useListViewDropZone() {
export default function useListViewDropZone( dropZoneName ) {
const {
getBlockRootClientId,
getBlockIndex,
getBlockCount,
getDraggedBlockClientIds,
canInsertBlocks,
} = useSelect( blockEditorStore );
getDraggedBlocksTargets,
} = unlock( useSelect( blockEditorStore ) );
const [ target, setTarget ] = useState();
const { rootClientId: targetRootClientId, blockIndex: targetBlockIndex } =
target || {};

const onBlockDrop = useOnBlockDrop( targetRootClientId, targetBlockIndex );
const onBlockDrop = useOnBlockDrop( targetRootClientId, targetBlockIndex, {
dropZoneName,
} );

const draggedBlockClientIds = getDraggedBlockClientIds();
const throttled = useThrottle(
useCallback(
( event, currentTarget ) => {
const draggedBlocksTargets = getDraggedBlocksTargets();

if (
draggedBlocksTargets?.length &&
! draggedBlocksTargets.includes( dropZoneName )
) {
// If drag targets are defined and the drop zone doesn't match, don't allow dropping.
return;
}
const position = { x: event.clientX, y: event.clientY };
const isBlockDrag = !! draggedBlockClientIds?.length;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# useBlockDropZone

`useBlockDropZone` is a React hook used to specify a drop zone for a block. This drop zone supports the drag and drop of media into the editor.

## Props

### dropZoneName

- Type: `string`
- Required: No

An optional name for the dropzone. Note that if a name is provided then only `<BlockDraggable>`s that specify this name in their `targets` prop will be able to be dropped.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
isPointContainedByRect,
} from '../../utils/math';
import { store as blockEditorStore } from '../../store';
import { unlock } from '../../experiments';

/** @typedef {import('../../utils/math').WPPoint} WPPoint */
/** @typedef {import('../use-on-block-drop/types').WPDropOperation} WPDropOperation */
Expand Down Expand Up @@ -141,6 +142,7 @@ export default function useBlockDropZone( {
// values returned by the `getRootBlockClientId` selector, which also uses
// an empty string to represent top-level blocks.
rootClientId: targetRootClientId = '',
dropZoneName,
} = {} ) {
const [ dropTarget, setDropTarget ] = useState( {
index: null,
Expand All @@ -166,17 +168,33 @@ export default function useBlockDropZone( {
[ targetRootClientId ]
);

const { getBlockListSettings, getBlocks, getBlockIndex } =
useSelect( blockEditorStore );
const {
getBlockListSettings,
getBlocks,
getBlockIndex,
getDraggedBlocksTargets,
} = unlock( useSelect( blockEditorStore ) );

const { showInsertionPoint, hideInsertionPoint } =
useDispatch( blockEditorStore );

const onBlockDrop = useOnBlockDrop( targetRootClientId, dropTarget.index, {
operation: dropTarget.operation,
dropZoneName,
} );
const throttled = useThrottle(
useCallback(
( event, ownerDocument ) => {
const draggedBlocksTargets = getDraggedBlocksTargets();

if (
draggedBlocksTargets?.length &&
! draggedBlocksTargets.includes( dropZoneName )
) {
// If drag targets are defined and the drop zone doesn't match, don't allow dropping.
return;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exiting early here means that no drop zone UI affordances are displayed when dragging. For example the blue lines that appear when dragging over a drop zone will not show.

}

const blocks = getBlocks( targetRootClientId );

// The block list is empty, don't show the insertion point but still allow dropping.
Expand Down
17 changes: 14 additions & 3 deletions packages/block-editor/src/components/use-on-block-drop/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function parseDropEvent( event ) {
srcIndex: null,
type: null,
blocks: null,
targets: null,
};

if ( ! event.dataTransfer ) {
Expand Down Expand Up @@ -61,6 +62,7 @@ export function parseDropEvent( event ) {
* @param {Function} moveBlocks A function that moves blocks.
* @param {Function} insertOrReplaceBlocks A function that inserts or replaces blocks.
* @param {Function} clearSelectedBlock A function that clears block selection.
* @param {string} dropZoneName The dropZoneName where the drop event will occur (e.g. 'list-view', 'canvas)
* @return {Function} The event handler for a block drop event.
*/
export function onBlockDrop(
Expand All @@ -70,14 +72,16 @@ export function onBlockDrop(
getClientIdsOfDescendants,
moveBlocks,
insertOrReplaceBlocks,
clearSelectedBlock
clearSelectedBlock,
dropZoneName
) {
return ( event ) => {
const {
srcRootClientId: sourceRootClientId,
srcClientIds: sourceClientIds,
type: dropType,
blocks,
targets: draggableTargets,
} = parseDropEvent( event );

// If the user is inserting a block.
Expand All @@ -91,6 +95,12 @@ export function onBlockDrop(

// If the user is moving a block.
if ( dropType === 'block' ) {
if (
draggableTargets?.length &&
! draggableTargets.includes( dropZoneName )
) {
return;
}
const sourceBlockIndex = getBlockIndex( sourceClientIds[ 0 ] );

// If the user is dropping to the same position, return early.
Expand Down Expand Up @@ -210,7 +220,7 @@ export default function useOnBlockDrop(
targetBlockIndex,
options = {}
) {
const { operation = 'insert' } = options;
const { operation = 'insert', dropZoneName } = options;
const hasUploadPermissions = useSelect(
( select ) => select( blockEditorStore ).getSettings().mediaUpload,
[]
Expand Down Expand Up @@ -307,7 +317,8 @@ export default function useOnBlockDrop(
getClientIdsOfDescendants,
moveBlocks,
insertOrReplaceBlocks,
clearSelectedBlock
clearSelectedBlock,
dropZoneName
);
const _onFilesDrop = onFilesDrop(
targetRootClientId,
Expand Down
Loading