-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Reorder blocks via drag & drop (v2. using editor dropzones). #4115
Merged
youknowriad
merged 84 commits into
WordPress:master
from
chriskmnds:add/drag-n-drop-for-blocks--dropzone
Apr 3, 2018
Merged
Changes from all commits
Commits
Show all changes
84 commits
Select commit
Hold shift + click to select a range
b62160a
Drag & Drop: Added functionality for reindexing blocks.
chriskmnds 7723d70
Drag & Drop for blocks: Added drag listeners and styling for dragging…
chriskmnds 51cd0f5
Drag & Drop for blocks: Updated drop logic to improve precision of th…
chriskmnds 82e9886
Drag & Drop for blocks: Resolved conflicts with latest master.
chriskmnds d6df73d
Drag & Drop for blocks: Added z-index to block underlay used in dragg…
chriskmnds f38db58
Drag & Drop for blocks: Changed background color of block inset/underlay
chriskmnds 1b04b5b
Drag & Drop for blocks: Fixed visible grayed inset area when dragging.
chriskmnds 9f25b10
Drag & Drop for blocks: Removed unnecessary block container element.
chriskmnds 0a23609
Drag & Drop for blocks: Updated cursor when hovering/dragging a
chriskmnds 9f3b3af
Drag & Drop for blocks: Added z-index property to placeholder compone…
chriskmnds 52ac1ff
Drag & Drop for blocks: Updated dragging inset margin to be effective…
chriskmnds c55ebc3
Merge branch 'master' into add/drag-n-drop-for-blocks--dropzone
chriskmnds 6c7a913
Drag & Drop for blocks: Fixed issues that surfaced since merging late…
chriskmnds 8dbc69e
Drag & Drop for blocks: Some cleanup - renamed 'underlay' to 'drag-in…
chriskmnds 35f9923
Drag & Drop for blocks: Changed dataTransfer data type for moving blo…
chriskmnds 9271649
Drag & Drop for blocks: Improved conditioning for onDrop handler.
chriskmnds 8fdcd6e
Drag & Drop for blocks: Added a small margin between cursor and drag
chriskmnds 1e2ae5e
Drag & Drop for blocks: Removed browser prefix for cursor style.
chriskmnds ec5052c
Drag & Drop for blocks: IE11 tweaks. Added conditional call to
chriskmnds 62afc6d
Drag & Drop for blocks: Some styling cleanup.
chriskmnds 8f13212
Drag & Drop for blocks: IE11/Safari tweaks.
chriskmnds 883ab19
Drag & Drop for blocks: Updated logic for setting drag image.
chriskmnds 6faceef
Drag & Drop for blocks: Updated drag image logic to be consistent across
chriskmnds 4978348
Drag & Drop for blocks: Updated drag image shadows and styling.
chriskmnds b512271
Drag & Drop for blocks: Updated logic for controlling the visibility of
chriskmnds e2c2e61
Drag & Drop for blocks: Some cleanup after code review.
chriskmnds 8436f21
Drag & Drop for blocks: Added more draggable handles to the block, in…
chriskmnds f131740
Drag & Drop for blocks: Some cleanup.
chriskmnds 2599d42
Drag & Drop for blocks: Updated cursor to move/grab for the block-set…
chriskmnds 6ad1c44
Drag & Drop for blocks: Cleanup from previous commit - set the cursor…
chriskmnds 6fc98de
Drag & Drop for blocks: Updated dragging logic to move clone around i…
chriskmnds 178cc60
Drag & Drop for blocks: Some cleanup - removed logging, and added a h…
chriskmnds c98ea9c
Drag & Drop for blocks: Cleared linting errors.
chriskmnds 6c2bf61
Drag & Drop for blocks: Updated drag start/end handlers to stop propa…
chriskmnds ab68b11
Drag & Drop for blocks: Added a fake/invisible drag image to avoid
chriskmnds 702eec3
Drag & Drop for blocks: Added transformation to the block clone if
chriskmnds 7cbdfde
Drag & Drop for blocks: Some code cleanup.
chriskmnds b0f0b0e
Drag & Drop for blocks: Updated block-mover and dropdown components to
chriskmnds 43ec29a
Drag & Drop for blocks: Some cleanup - inline comments, new lines, etc.
chriskmnds ec53f4f
Drag & Drop for blocks: Making sure the DOM is not updated prior to r…
chriskmnds 2e0b13a
Drag & Drop for blocks: Moved drag init/end logic to a higher order
chriskmnds a2246f4
Drag & Drop for blocks: Cleanup.
chriskmnds db7a34b
Drag & Drop for blocks: Resolved conflicts with master. Several updat…
chriskmnds d8bedee
Drag & Drop for blocks: Cleanup. Removed leftover styles.
chriskmnds 25485f9
Drag & Drop for blocks: Code cleanup.
chriskmnds 05f2d10
Drag & Drop for blocks: Code cleanup. Further extracted drag init/end
chriskmnds f33d4db
Drag & Drop for blocks: Resolved conflicts.
chriskmnds a83c738
Drag & Drop for blocks: Updated cursor styling to grab for the new bl…
chriskmnds 30ee308
Drag & Drop for blocks: Updates after PR feedback.
chriskmnds da001c9
Drag & Drop for blocks: Some cleanup after PR feedback. removed unnec…
chriskmnds b071cc4
Drag & Drop for blocks: Updated cursor styling after PR feedback to g…
chriskmnds 46ed7a2
Drag & Drop for blocks: Updated index of reusable block edit panel to…
chriskmnds 129dac9
Drag & Drop for blocks: Some cleanup after PR feedback. Renamed _stat…
chriskmnds d6897ce
Drag & Drop for blocks: Added support for inner blocks.
chriskmnds 4e66a4a
Drag & Drop for blocks: Cleanup - lint errors.
chriskmnds bfb78c1
Drag & Drop for blocks: Updates after PR review. No longer passing drag
chriskmnds 0a6a05d
Drag & Drop for blocks: Got rid of BLOCK_REORDER constant and selector.
chriskmnds f72e05d
Drag & Drop for blocks: Updates and cleanup after PR review.
chriskmnds f03e4ec
Drag & Drop for blocks: Cleanup. Removed commented out code.
chriskmnds 650f613
Merge remote-tracking branch 'origin/master' into add/drag-n-drop-for…
youknowriad 5c1d339
Drag And Drop: Refactor using a component instead of Higher Order Com…
youknowriad 5c1613d
Clarify Drop Events
youknowriad 4d3b8b8
Fix dragging between nested and not nested blocks
youknowriad d731edf
Fix Drag Area and Styling
youknowriad 9624dca
More cleaning and fix insert position
youknowriad 94dd1cc
Extract BlockDraggable Component
youknowriad 7f01121
Destructre the uid block prop
youknowriad 9982bcd
Clarify BlockDraggable classnames
youknowriad 5623a71
Clarify Draggable Component Docs
youknowriad f48d65a
Avoid creating a new block object if the layout didn't change when mo…
youknowriad d415a1f
Less generic body className
youknowriad 0eca12c
Bind BlockDropZone event handlers to avoid rerenderings
youknowriad e763e28
Remove Block UI on the cloned block element which fixes drag and scroll
youknowriad f19dcd0
Decrease the opacity of the cloned draggable
youknowriad 48f8087
Updating ReactTextareraAutosize to fix a bug on initial load (long po…
youknowriad 2759481
Remove iframes from the clone to fix embed's drag and drop
youknowriad 51c1cef
Fix multiple dropzones showing up in Gallery block
youknowriad 794dfd0
Merge remote-tracking branch 'origin/master' into add/drag-n-drop-for…
youknowriad 4a817c5
Changes per review
youknowriad 7dcc860
Fix scrolling when dragging
youknowriad b20b033
Remove useless styles
youknowriad c61c770
Merge remote-tracking branch 'origin/master' into add/drag-n-drop-for…
youknowriad d5daf30
Fix wide blocks scroll
youknowriad a648f67
Fix typos in documentation
youknowriad File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# Draggable | ||
|
||
`Draggable` is a Component that can wrap any element to make it draggable. When used, a cross-browser (including IE) customisable drag image is created. The component clones the specified element on drag-start and uses the clone as a drag image during drag-over. Discards the clone on drag-end. | ||
|
||
## Props | ||
|
||
The component accepts the following props: | ||
|
||
### elementId | ||
|
||
The HTML id of the element to clone on drag | ||
|
||
- Type: `string` | ||
- Required: Yes | ||
|
||
### transferData | ||
|
||
Arbitrary data object attached to the drag and drop event. | ||
|
||
- Type: `Object` | ||
- Required: Yes | ||
|
||
### onDragStart | ||
|
||
The function called when dragging starts. | ||
|
||
- Type: `Function` | ||
- Required: No | ||
- Default: `noop` | ||
|
||
### onDragEnd | ||
|
||
The function called when dragging ends. | ||
|
||
- Type: `Function` | ||
- Required: No | ||
- Default: `noop` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { noop } from 'lodash'; | ||
import classnames from 'classnames'; | ||
|
||
/** | ||
* WordPress Dependencies | ||
*/ | ||
import { Component } from '@wordpress/element'; | ||
|
||
/** | ||
* Internal Dependencies | ||
*/ | ||
import withSafeTimeout from '../higher-order/with-safe-timeout'; | ||
import './style.scss'; | ||
|
||
const dragImageClass = 'components-draggable__invisible-drag-image'; | ||
const cloneWrapperClass = 'components-draggable__clone'; | ||
const cloneHeightTransformationBreakpoint = 700; | ||
const clonePadding = 20; | ||
|
||
class Draggable extends Component { | ||
constructor() { | ||
super( ...arguments ); | ||
this.onDragStart = this.onDragStart.bind( this ); | ||
this.onDragOver = this.onDragOver.bind( this ); | ||
this.onDragEnd = this.onDragEnd.bind( this ); | ||
} | ||
|
||
componentWillUnmount() { | ||
this.removeDragClone(); | ||
} | ||
|
||
/** | ||
* Removes the element clone, resets cursor, and removes drag listener. | ||
* @param {Object} event The non-custom DragEvent. | ||
*/ | ||
onDragEnd( event ) { | ||
const { onDragEnd = noop } = this.props; | ||
this.removeDragClone(); | ||
// Reset cursor. | ||
document.body.classList.remove( 'is-dragging-components-draggable' ); | ||
event.preventDefault(); | ||
|
||
this.props.setTimeout( onDragEnd ); | ||
} | ||
|
||
/* | ||
* Updates positioning of element clone based on mouse movement during dragging. | ||
* @param {Object} event The non-custom DragEvent. | ||
*/ | ||
onDragOver( event ) { | ||
this.cloneWrapper.style.top = | ||
`${ parseInt( this.cloneWrapper.style.top, 10 ) + event.clientY - this.cursorTop }px`; | ||
this.cloneWrapper.style.left = | ||
`${ parseInt( this.cloneWrapper.style.left, 10 ) + event.clientX - this.cursorLeft }px`; | ||
|
||
// Update cursor coordinates. | ||
this.cursorLeft = event.clientX; | ||
this.cursorTop = event.clientY; | ||
} | ||
|
||
/** | ||
* - Clones the current element and spawns clone over original element. | ||
* - Adds a fake temporary drag image to avoid browser defaults. | ||
* - Sets transfer data. | ||
* - Adds dragover listener. | ||
* @param {Object} event The non-custom DragEvent. | ||
* @param {string} elementId The HTML id of the element to be dragged. | ||
* @param {Object} transferData The data to be set to the event's dataTransfer - to be accessible in any later drop logic. | ||
*/ | ||
onDragStart( event ) { | ||
const { elementId, transferData, onDragStart = noop } = this.props; | ||
const element = document.getElementById( elementId ); | ||
if ( ! element ) { | ||
event.preventDefault(); | ||
return; | ||
} | ||
|
||
// Set a fake drag image to avoid browser defaults. Remove from DOM | ||
// right after. event.dataTransfer.setDragImage is not supported yet in | ||
// IE, we need to check for its existence first. | ||
if ( 'function' === typeof event.dataTransfer.setDragImage ) { | ||
const dragImage = document.createElement( 'div' ); | ||
dragImage.id = `drag-image-${ elementId }`; | ||
dragImage.classList.add( dragImageClass ); | ||
document.body.appendChild( dragImage ); | ||
event.dataTransfer.setDragImage( dragImage, 0, 0 ); | ||
this.props.setTimeout( () => { | ||
document.body.removeChild( dragImage ); | ||
} ); | ||
} | ||
|
||
event.dataTransfer.setData( 'text', JSON.stringify( transferData ) ); | ||
|
||
// Prepare element clone and append to element wrapper. | ||
const elementRect = element.getBoundingClientRect(); | ||
const elementWrapper = element.parentNode; | ||
const elementTopOffset = parseInt( elementRect.top, 10 ); | ||
const elementLeftOffset = parseInt( elementRect.left, 10 ); | ||
const clone = element.cloneNode( true ); | ||
clone.id = `clone-${ elementId }`; | ||
this.cloneWrapper = document.createElement( 'div' ); | ||
this.cloneWrapper.classList.add( cloneWrapperClass ); | ||
this.cloneWrapper.style.width = `${ elementRect.width + ( clonePadding * 2 ) }px`; | ||
|
||
if ( elementRect.height > cloneHeightTransformationBreakpoint ) { | ||
// Scale down clone if original element is larger than 700px. | ||
this.cloneWrapper.style.transform = 'scale(0.5)'; | ||
this.cloneWrapper.style.transformOrigin = 'top left'; | ||
// Position clone near the cursor. | ||
this.cloneWrapper.style.top = `${ event.clientY - 100 }px`; | ||
this.cloneWrapper.style.left = `${ event.clientX }px`; | ||
} else { | ||
// Position clone right over the original element (20px padding). | ||
this.cloneWrapper.style.top = `${ elementTopOffset - clonePadding }px`; | ||
this.cloneWrapper.style.left = `${ elementLeftOffset - clonePadding }px`; | ||
} | ||
|
||
// Hack: Remove iFrames as it's causing the embeds drag clone to freeze | ||
[ ...clone.querySelectorAll( 'iframe' ) ].forEach( child => child.parentNode.removeChild( child ) ); | ||
|
||
this.cloneWrapper.appendChild( clone ); | ||
elementWrapper.appendChild( this.cloneWrapper ); | ||
|
||
// Mark the current cursor coordinates. | ||
this.cursorLeft = event.clientX; | ||
this.cursorTop = event.clientY; | ||
// Update cursor to 'grabbing', document wide. | ||
document.body.classList.add( 'is-dragging-components-draggable' ); | ||
document.addEventListener( 'dragover', this.onDragOver ); | ||
|
||
this.props.setTimeout( onDragStart ); | ||
} | ||
|
||
removeDragClone() { | ||
document.removeEventListener( 'dragover', this.onDragOver ); | ||
if ( this.cloneWrapper && this.cloneWrapper.parentNode ) { | ||
// Remove clone. | ||
this.cloneWrapper.parentNode.removeChild( this.cloneWrapper ); | ||
this.cloneWrapper = null; | ||
} | ||
} | ||
|
||
render() { | ||
const { children, className } = this.props; | ||
return ( | ||
<div | ||
className={ classnames( 'components-draggable', className ) } | ||
onDragStart={ this.onDragStart } | ||
onDragEnd={ this.onDragEnd } | ||
draggable | ||
> | ||
{ children } | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default withSafeTimeout( Draggable ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
body.is-dragging-components-draggable { | ||
cursor: move;/* Fallback for IE/Edge < 14 */ | ||
cursor: grabbing !important; | ||
} | ||
|
||
.components-draggable__invisible-drag-image { | ||
position: fixed; | ||
left: -1000px; | ||
height: 50px; | ||
width: 50px; | ||
} | ||
|
||
.components-draggable__clone { | ||
position: fixed; | ||
padding: 20px; | ||
background: transparent; | ||
pointer-events: none; | ||
z-index: z-index( '.components-draggable__clone' ); | ||
opacity: 0.8; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# DropZone | ||
|
||
`DropZone` is a Component creating a drop zone area taking the full size of its parent element. It supports dropping files, HTML content or any other HTML drop event. To work properly this components needs to be wrapped in a `DropZoneProvider`. | ||
|
||
## Usage | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicate heading. |
||
|
||
```jsx | ||
import { DropZoneProvider, DropZone } from '@wordpress/components'; | ||
|
||
function MyComponent() { | ||
return ( | ||
<DropZoneProvider> | ||
<div> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the intermediate |
||
<DropZone onDrop={ () => console.log( 'do something' ) } /> | ||
</div> | ||
</DropZoneProvider> | ||
); | ||
} | ||
``` | ||
|
||
## Props | ||
|
||
The component accepts the following props: | ||
|
||
### onFilesDrop | ||
|
||
The function is called when dropping a file into the `DropZone`. It receives two arguments: an array of dropped files and a position object which the following shape: `{ x: 'left|right', y: 'top|bottom' }`. The position object indicates whether the drop event happened closer to the top or bottom edges and left or right ones. | ||
|
||
- Type: `Function` | ||
- Required: No | ||
- Default: `noop` | ||
|
||
### onHTMLDrop | ||
|
||
The function is called when dropping a file into the `DropZone`. It receives two arguments: the HTML being dropped and a position object. | ||
|
||
- Type: `Function` | ||
- Required: No | ||
- Default: `noop` | ||
|
||
### onDrop | ||
|
||
The function is generic drop handler called if the `onFilesDrop` or `onHTMLDrop` are not called. It receives two arguments: The drop `event` object and the position object. | ||
|
||
- Type: `Function` | ||
- Required: No | ||
- Default: `noop` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally, in React, we'd use
ref
for this sort of thing. I understand that BlockListBlock is a bit different in that BlockDraggable is nested in the element itself via IgnoreNestedEvents, thus making it trickier to grab and pass the right ref. Edit: Actually, passing the ID string may make this component more easily reusable 👍.If we stick to passing
elementId
, which seems fine, should we make sure thatelement
exists?