diff --git a/docs/reference/deprecated.md b/docs/reference/deprecated.md
index 0f8b1dabe43fc2..115161f0762c7f 100644
--- a/docs/reference/deprecated.md
+++ b/docs/reference/deprecated.md
@@ -7,6 +7,7 @@ Gutenberg's deprecation policy is intended to support backwards-compatibility fo
## 3.7.0
+ - `wp.components.Draggable` has been deprecated. Please, use `wp.components.withDraggable` instead.
- `wp.components.withAPIData` has been removed. Please use the Core Data module or `wp.apiFetch` directly instead.
- `wp.data.dispatch("core").receiveTerms` has been deprecated. Please use `wp.data.dispatch("core").receiveEntityRecords` instead.
- `getCategories` resolvers has been deprecated. Please use `getEntityRecords` resolver instead.
diff --git a/edit-post/assets/stylesheets/_z-index.scss b/edit-post/assets/stylesheets/_z-index.scss
index 9a350e6868e7f6..c560caf2bfc97f 100644
--- a/edit-post/assets/stylesheets/_z-index.scss
+++ b/edit-post/assets/stylesheets/_z-index.scss
@@ -9,7 +9,7 @@ $z-layers: (
".editor-block-list__block {core/image aligned left or right}": 20,
".editor-block-list__block {core/image aligned wide or fullwide}": 20,
".freeform-toolbar": 10,
- ".editor-block-list__breadcrumb": 1,
+ ".editor-block-list__breadcrumb": 100, // drag handler, to be shown below any overlay
".components-form-toggle__input": 1,
".editor-inserter__tabs": 1,
".editor-inserter__tab.is-active": 1,
@@ -34,10 +34,7 @@ $z-layers: (
// Active pill button
".components-button.is-button {:focus or .is-primary}": 1,
- // Should have lower index than anything else positioned inside the block containers
- ".editor-block-list__block-draggable": 0,
-
- // The draggable element should show up above the entire UI
+ // The drag image should show up above the entire UI
".components-draggable__clone": 1000000000,
// Should have higher index than the inset/underlay used for dragging
diff --git a/packages/components/src/dashicon/index.js b/packages/components/src/dashicon/index.js
index c103795736e638..4541f1dc56ecfd 100644
--- a/packages/components/src/dashicon/index.js
+++ b/packages/components/src/dashicon/index.js
@@ -15,12 +15,13 @@ export default class Dashicon extends Component {
return (
this.props.icon !== nextProps.icon ||
this.props.size !== nextProps.size ||
+ this.props.viewBox !== nextProps.viewBox ||
this.props.className !== nextProps.className
);
}
render() {
- const { icon, className, size = 20 } = this.props;
+ const { icon, className, size = 20, viewBox = 20 } = this.props;
let path;
switch ( icon ) {
@@ -893,7 +894,7 @@ export default class Dashicon extends Component {
xmlns="http://www.w3.org/2000/svg"
width={ size }
height={ size }
- viewBox="0 0 20 20"
+ viewBox={ '0 0 ' + viewBox + ' ' + viewBox } // TODO: if we like this approach, remove this hack and implement upstream
>
diff --git a/packages/components/src/draggable/index.js b/packages/components/src/draggable/index.js
index f6c85d6d40834b..63e2e50b5bc175 100644
--- a/packages/components/src/draggable/index.js
+++ b/packages/components/src/draggable/index.js
@@ -9,6 +9,7 @@ import classnames from 'classnames';
*/
import { Component } from '@wordpress/element';
import { withSafeTimeout } from '@wordpress/compose';
+import deprecated from '@wordpress/deprecated';
const dragImageClass = 'components-draggable__invisible-drag-image';
const cloneWrapperClass = 'components-draggable__clone';
@@ -19,6 +20,11 @@ class Draggable extends Component {
constructor() {
super( ...arguments );
+ deprecated( 'wp.components.Draggable', {
+ version: 3.7,
+ alternative: 'wp.components.withDraggable',
+ } );
+
this.onDragStart = this.onDragStart.bind( this );
this.onDragOver = this.onDragOver.bind( this );
this.onDragEnd = this.onDragEnd.bind( this );
diff --git a/packages/components/src/higher-order/with-draggable/README.md b/packages/components/src/higher-order/with-draggable/README.md
new file mode 100644
index 00000000000000..4b12a4afca90ab
--- /dev/null
+++ b/packages/components/src/higher-order/with-draggable/README.md
@@ -0,0 +1,79 @@
+# withDraggable
+
+`withDraggable` is a Higher-Order Component that provides a way to set up a cross-browser (including IE) customisable drag image, and the transfer data for the drag event. It decouples the drag handle and the element to drag: it wraps the component that will be the drag handle, and it should be provided the DOM ID of the element to drag.
+
+Note that the drag handle needs to declare the `draggable="true"` property. The `withDraggable` component only takes care of the logic to setup the drag image and the transfer data, but is not concerned with creating an actual DOM element that is draggable.
+
+## Props
+
+The component injects the following props into the wrapped component:
+
+### initDragging
+
+A function that initializes the drag event, setting up the transfer data and creating the drag image. It returns a function to be called on the `dragstart` DOM event.
+
+- Type: `Function`
+- Required: Yes
+- Arguments:
+ - `elementId`: DOM id of the element to be dragged
+ - `data`: the [data](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/dataTransfer) to be transfered by the event
+
+## Usage
+
+```jsx
+import { Dashicon, Panel, PanelBody, withDraggable } from '@wordpress/components';
+
+const MyDraggable = ( { initDragging } ) => (
+
+);
+
+export default withDraggable( MyDraggable );
+```
+
+If the wrapped element would want to inject their own logic into the `dragstart` event, it should initialize the `initDragging` prop provided by `withDraggable` in that event handler. For example:
+
+```jsx
+
+import { Dashicon, Panel, PanelBody, withDraggable } from '@wordpress/components';
+
+const class MyDraggable extends Component {
+
+ constructor() {
+ super( ...arguments );
+ this.myDragStart = this.myDragStart.bind( this );
+ }
+
+ myDragStart( event ){
+ this.props.initDragging( 'draggable-panel', {} )( event );
+
+ // can do whatever is necessary after the event has been set up
+ }
+
+ render( ) {
+ return (
+
+ );
+ }
+}
+
+export default withDraggable( MyDraggable );
+```
\ No newline at end of file
diff --git a/packages/components/src/higher-order/with-draggable/index.js b/packages/components/src/higher-order/with-draggable/index.js
new file mode 100644
index 00000000000000..16ab27997d5531
--- /dev/null
+++ b/packages/components/src/higher-order/with-draggable/index.js
@@ -0,0 +1,154 @@
+/**
+ * WordPress Dependencies
+ */
+import { Component } from '@wordpress/element';
+import { createHigherOrderComponent } from '@wordpress/compose';
+
+const dragImageClass = 'components-draggable__invisible-drag-image';
+const cloneWrapperClass = 'components-draggable__clone';
+const cloneHeightTransformationBreakpoint = 700;
+const clonePadding = 20;
+
+const withDraggable = createHigherOrderComponent(
+ ( OriginalComponent ) => {
+ return class extends Component {
+ constructor() {
+ super( ...arguments );
+ this.onDragStart = this.onDragStart.bind( this );
+ this.onDragOver = this.onDragOver.bind( this );
+ this.onDragEnd = this.onDragEnd.bind( this );
+ this.resetDragState = this.resetDragState.bind( this );
+ }
+
+ componentWillUnmount() {
+ this.resetDragState();
+ }
+
+ /**
+ * Function that creates the dragstart event handler.
+ *
+ * @param {string} elementId The HTML id of the element to be dragged.
+ * @param {Object} data The data to be set to the event's dataTransfer - to be accessible in any later drop logic.
+ * @return {function} A function for wrapped components to use as their onDragStart handler.
+ */
+ onDragStart( elementId, data ) {
+ return ( event ) => {
+ const element = document.getElementById( elementId );
+ if ( ! element || ! data ) {
+ event.preventDefault();
+ return;
+ }
+
+ event.dataTransfer.setData( 'text', JSON.stringify( data ) );
+
+ // 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 );
+ setTimeout( () => {
+ document.body.removeChild( dragImage );
+ } );
+ }
+
+ // 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' );
+
+ // connect listeners
+ document.addEventListener( 'dragover', this.onDragOver );
+ document.addEventListener( 'dragend', this.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;
+ }
+
+ /**
+ * Removes the element clone, resets cursor, and removes drag listener.
+ */
+ onDragEnd( ) {
+ this.resetDragState();
+ }
+
+ /**
+ * Cleans up drag state when drag has completed, or component unmounts
+ * while dragging.
+ */
+ resetDragState() {
+ // Remove listeners
+ document.removeEventListener( 'dragover', this.onDragOver );
+ document.removeEventListener( 'dragend', this.onDragEnd );
+
+ // Remove drag clone
+ if ( this.cloneWrapper && this.cloneWrapper.parentNode ) {
+ this.cloneWrapper.parentNode.removeChild( this.cloneWrapper );
+ this.cloneWrapper = null;
+ }
+
+ // Reset cursor.
+ document.body.classList.remove( 'is-dragging-components-draggable' );
+ }
+
+ render() {
+ return (
+
+ );
+ }
+ };
+ },
+ 'withDraggable'
+);
+
+export default withDraggable;
diff --git a/packages/components/src/higher-order/with-draggable/style.scss b/packages/components/src/higher-order/with-draggable/style.scss
new file mode 100644
index 00000000000000..ad046f4e092a36
--- /dev/null
+++ b/packages/components/src/higher-order/with-draggable/style.scss
@@ -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;
+}
diff --git a/packages/components/src/index.js b/packages/components/src/index.js
index 182cb6e111c0d3..402e220828286a 100644
--- a/packages/components/src/index.js
+++ b/packages/components/src/index.js
@@ -62,6 +62,7 @@ export { default as navigateRegions } from './higher-order/navigate-regions';
export { default as withAPIData } from './higher-order/with-api-data';
export { default as withContext } from './higher-order/with-context';
export { default as withConstrainedTabbing } from './higher-order/with-constrained-tabbing';
+export { default as withDraggable } from './higher-order/with-draggable';
export { default as withFallbackStyles } from './higher-order/with-fallback-styles';
export { default as withFilters } from './higher-order/with-filters';
export { default as withFocusOutside } from './higher-order/with-focus-outside';
diff --git a/packages/editor/src/components/block-list/block-draggable.js b/packages/editor/src/components/block-list/block-draggable.js
deleted file mode 100644
index 67f77189b36bd2..00000000000000
--- a/packages/editor/src/components/block-list/block-draggable.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * External dependencies
- */
-import classnames from 'classnames';
-
-/**
- * WordPress dependencies
- */
-import { Draggable } from '@wordpress/components';
-
-function BlockDraggable( { rootClientId, index, clientId, layout, isDragging, ...props } ) {
- const className = classnames( 'editor-block-list__block-draggable', {
- 'is-visible': isDragging,
- } );
-
- const transferData = {
- type: 'block',
- fromIndex: index,
- rootClientId,
- clientId,
- layout,
- };
-
- return (
-
-
-
- );
-}
-
-export default BlockDraggable;
diff --git a/packages/editor/src/components/block-list/block.js b/packages/editor/src/components/block-list/block.js
index 4d32a8069dc125..91fd06c5611b40 100644
--- a/packages/editor/src/components/block-list/block.js
+++ b/packages/editor/src/components/block-list/block.js
@@ -44,7 +44,6 @@ import BlockContextualToolbar from './block-contextual-toolbar';
import BlockMultiControls from './multi-controls';
import BlockMobileToolbar from './block-mobile-toolbar';
import BlockInsertionPoint from './insertion-point';
-import BlockDraggable from './block-draggable';
import IgnoreNestedEvents from './ignore-nested-events';
import InserterWithShortcuts from '../inserter-with-shortcuts';
import Inserter from '../inserter';
@@ -394,7 +393,7 @@ export class BlockListBlock extends Component {
// We render block movers and block settings to keep them tabbale even if hidden
const shouldRenderMovers = ( isSelected || hoverArea === 'left' ) && ! showEmptyBlockSideInserter && ! isMultiSelecting && ! isPartOfMultiSelection && ! isTypingWithinBlock;
const shouldRenderBlockSettings = ( isSelected || hoverArea === 'right' ) && ! isMultiSelecting && ! isPartOfMultiSelection;
- const shouldShowBreadcrumb = isHovered && ! isEmptyDefaultBlock;
+ const shouldShowBreadcrumb = ( isSelected || isHovered ) && ! isEmptyDefaultBlock;
const shouldShowContextualToolbar = ! showSideInserter && ( ( isSelected && ! isTypingWithinBlock && isValid ) || isFirstMultiSelected ) && ( ! hasFixedToolbar || ! isLargeViewport );
const shouldShowMobileToolbar = shouldAppearSelected;
const { error, dragging } = this.state;
@@ -481,18 +480,6 @@ export class BlockListBlock extends Component {
] }
{ ...wrapperProps }
>
- { ! isPartOfMultiSelection && isMovable && (
-
- ) }
{ shouldShowInsertionPoint && (
) }
{ shouldShowContextualToolbar && }
diff --git a/packages/editor/src/components/block-list/breadcrumb.js b/packages/editor/src/components/block-list/breadcrumb.js
index ca14b1d346c947..cb182c36e5c53e 100644
--- a/packages/editor/src/components/block-list/breadcrumb.js
+++ b/packages/editor/src/components/block-list/breadcrumb.js
@@ -2,7 +2,7 @@
* WordPress dependencies
*/
import { Component, Fragment } from '@wordpress/element';
-import { Toolbar } from '@wordpress/components';
+import { Dashicon, Toolbar, withDraggable } from '@wordpress/components';
import { withSelect } from '@wordpress/data';
import { compose } from '@wordpress/compose';
@@ -28,6 +28,8 @@ export class BlockBreadcrumb extends Component {
};
this.onFocus = this.onFocus.bind( this );
this.onBlur = this.onBlur.bind( this );
+ this.onDragStart = this.onDragStart.bind( this );
+ this.onDragEnd = this.onDragEnd.bind( this );
}
onFocus( event ) {
@@ -47,12 +49,27 @@ export class BlockBreadcrumb extends Component {
} );
}
+ onDragStart( event ) {
+ const { draggableData, draggableElementId, initDragging, onDragStart } = this.props;
+ initDragging( draggableElementId, draggableData )( event );
+ onDragStart( event );
+ }
+
+ onDragEnd( event ) {
+ this.props.onDragEnd( event );
+ }
+
render() {
- const { clientId, rootClientId } = this.props;
+ const { isDraggable, clientId, rootClientId } = this.props;
return (
-
+
+
{ rootClientId && (
@@ -70,9 +87,9 @@ export default compose( [
withSelect( ( select, ownProps ) => {
const { getBlockRootClientId } = select( 'core/editor' );
const { clientId } = ownProps;
-
return {
rootClientId: getBlockRootClientId( clientId ),
};
} ),
+ withDraggable,
] )( BlockBreadcrumb );
diff --git a/packages/editor/src/components/block-list/style.scss b/packages/editor/src/components/block-list/style.scss
index 31a991f0c4ef82..63765e5f8123e7 100644
--- a/packages/editor/src/components/block-list/style.scss
+++ b/packages/editor/src/components/block-list/style.scss
@@ -1,19 +1,4 @@
.editor-block-list__layout .components-draggable__clone {
- & > .editor-block-list__block > .editor-block-list__block-draggable {
- background: $white; // @todo: ensure this works with themes that invert the color
- box-shadow: $shadow-popover;
-
- @include break-small {
- left: -$block-parent-side-ui-clearance - $border-width;
- right: -$block-parent-side-ui-clearance - $border-width;
-
- .editor-block-list__layout & {
- left: -$border-width;
- right: -$border-width;
- }
- }
- }
-
// Hide the Block UI when dragging the block
// This ensures the page scroll properly (no sticky elements)
.editor-block-contextual-toolbar,
@@ -24,62 +9,6 @@
}
}
-.editor-block-list__layout .editor-block-list__block-draggable {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: z-index(".editor-block-list__block-draggable");
-
- > .editor-block-list__block-draggable-inner {
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- visibility: hidden;
-
- // Use opacity to work in various editor styles.
- background-color: $dark-opacity-light-200;
-
- .is-dark-theme & {
- background-color: $light-opacity-light-200;
- }
-
- @include break-small {
- margin: 0 48px;
- }
- }
-
- &.is-visible > .editor-block-list__block-draggable-inner {
- visibility: visible;
- }
-
- @include break-small {
- // use a wider available space for hovering/selecting/dragging on top level blocks
- left: -$parent-block-padding - $block-padding;
- right: -$parent-block-padding - $block-padding;
-
- // use smaller space for hovering/selecting/dragging on child blocks
- .editor-block-list__layout & {
- left: 0;
- right: 0;
- }
-
- // Full width blocks don't have the place to expand on the side
- .editor-block-list__block[data-align="full"] & {
- left: 0;
- right: 0;
- }
- }
-
-
- cursor: move; // Fallback for IE/Edge < 14
- cursor: grab;
-}
-
-
// Allow Drag & Drop when clicking on the empty area of the mover and the settings menu
.editor-block-list__layout .editor-block-list__block .editor-block-mover,
.editor-block-list__layout .editor-block-list__block .editor-block-settings-menu {
@@ -95,7 +24,7 @@
.editor-block-list__block {
&.is-hidden *,
&.is-hidden > * {
- visibility: hidden;
+ opacity: 0.8;
}
.editor-block-list__block-edit .reusable-block-edit-panel * {
@@ -423,12 +352,6 @@
float: left;
}
- // There is no side UI clearance on full-wide elements, so they are simply not draggable on the sides
- > .editor-block-list__block-draggable {
- left: 0;
- right: 0;
- }
-
// Position hover label on the right
> .editor-block-list__breadcrumb {
right: -$border-width;
@@ -1006,7 +929,14 @@
.editor-block-list__block:hover & {
@include fade_in(0.1s);
}
+
+ .editor-block-list__breadcrumb-drag-handle {
+ margin-right: 2px;
+ }
}
+
+ cursor: move;/* Fallback for IE/Edge < 14 */
+ cursor: grab;
}
.editor-block-list__descendant-arrow::before {