From 98c9fc3dab3c257f99a419f4e9c2ee3950e74954 Mon Sep 17 00:00:00 2001 From: Ella van Durpe Date: Fri, 26 Jun 2020 03:06:09 +0300 Subject: [PATCH 1/2] Drag and drop on long press --- .../components/block-list/block-wrapper.js | 132 ++++++++++++++++++ packages/components/src/drop-zone/provider.js | 10 +- 2 files changed, 140 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block-wrapper.js b/packages/block-editor/src/components/block-list/block-wrapper.js index 4cff24dca65de..9d50b82f0d760 100644 --- a/packages/block-editor/src/components/block-list/block-wrapper.js +++ b/packages/block-editor/src/components/block-list/block-wrapper.js @@ -13,6 +13,7 @@ import { useState, useContext, forwardRef, + createPortal, } from '@wordpress/element'; import { focus, isTextField, placeCaretAtHorizontalEdge } from '@wordpress/dom'; import { BACKSPACE, DELETE, ENTER } from '@wordpress/keycodes'; @@ -27,6 +28,27 @@ import useMovingAnimation from '../use-moving-animation'; import { Context, SetBlockNodes } from './root-container'; import { BlockListBlockContext } from './block'; import ELEMENTS from './block-wrapper-elements'; +import BlockDraggableChip from '../block-draggable/draggable-chip'; + +function useOnLongPress( ref, timeout, callback, deps ) { + useEffect( () => { + let timeoutId; + const set = ( event ) => { + clearTimeout( timeoutId ); + timeoutId = setTimeout( () => callback( event ), timeout ); + }; + const unset = () => { + clearTimeout( timeoutId ); + }; + ref.current.addEventListener( 'mousedown', set ); + ref.current.addEventListener( 'mouseup', unset ); + return () => { + ref.current.removeEventListener( 'mousedown', set ); + ref.current.removeEventListener( 'mouseup', unset ); + clearTimeout( timeoutId ); + }; + }, deps ); +} const BlockComponent = forwardRef( ( @@ -235,6 +257,111 @@ const BlockComponent = forwardRef( setHovered( false ); } + const [ isDraggging, setIsDragging ] = useState( false ); + const container = useRef( document.createElement( 'div' ) ); + + useOnLongPress( + wrapper, + 250, + () => { + if ( + // isSelected is oudated. + ! wrapper.current.classList.contains( 'is-selected' ) || + ! window.getSelection().isCollapsed + ) { + return; + } + + const cancel = () => { + window.removeEventListener( 'mouseup', cancel ); + document.removeEventListener( 'selectionchange', cancel ); + + wrapper.current.style.transform = ''; + wrapper.current.style.transition = ''; + }; + + window.addEventListener( 'mouseup', cancel ); + document.addEventListener( 'selectionchange', cancel ); + + wrapper.current.style.transform = 'scale(1.02)'; + wrapper.current.style.transition = 'transform .75s ease-in-out'; + }, + [] + ); + + useOnLongPress( + wrapper, + 1000, + ( _event ) => { + wrapper.current.style.transform = ''; + wrapper.current.style.transition = ''; + + if ( + ! wrapper.current.classList.contains( 'is-selected' ) || + ! window.getSelection().isCollapsed + ) { + return; + } + + const { parentNode } = wrapper.current; + + setIsDragging( true ); + wrapper.current.style.display = 'none'; + window.getSelection().removeAllRanges(); + parentNode.appendChild( container.current ); + container.current.style.position = 'fixed'; + container.current.style.pointerEvents = 'none'; + container.current.style.left = _event.clientX - 20 + 'px'; + container.current.style.top = _event.clientY + 20 + 'px'; + + const onMouseMove = ( event ) => { + const newEvent = new window.CustomEvent( 'dragover', { + bubbles: true, + detail: { + clientX: event.clientX, + clientY: event.clientY, + }, + } ); + window.dispatchEvent( newEvent ); + + container.current.style.left = event.clientX - 20 + 'px'; + container.current.style.top = event.clientY + 20 + 'px'; + }; + + const onMouseUp = ( event ) => { + window.removeEventListener( 'mousemove', onMouseMove ); + window.removeEventListener( 'mouseup', onMouseUp ); + + setIsDragging( false ); + + const dataTransfer = new window.DataTransfer(); + const data = { + type: 'block', + srcIndex: index, + srcClientId: clientId, + srcRootClientId: rootClientId || '', + }; + + dataTransfer.setData( 'text', JSON.stringify( data ) ); + + const newEvent = new window.DragEvent( 'drop', { + bubbles: true, + dataTransfer, + } ); + event.target.dispatchEvent( newEvent ); + parentNode.removeChild( container.current ); + + if ( wrapper.current ) { + wrapper.current.style.display = ''; + } + }; + + window.addEventListener( 'mousemove', onMouseMove ); + window.addEventListener( 'mouseup', onMouseUp ); + }, + [] + ); + return ( // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events { children } + { isDraggging && + createPortal( + , + container.current + ) } ); } diff --git a/packages/components/src/drop-zone/provider.js b/packages/components/src/drop-zone/provider.js index 43d9d859fd484..a971fdc57891f 100644 --- a/packages/components/src/drop-zone/provider.js +++ b/packages/components/src/drop-zone/provider.js @@ -59,6 +59,7 @@ class DropZoneProvider extends Component { // Event listeners this.onDragOver = this.onDragOver.bind( this ); + this.onMouseUp = this.onMouseUp.bind( this ); this.onDrop = this.onDrop.bind( this ); // Context methods so this component can receive data from consumers this.addDropZone = this.addDropZone.bind( this ); @@ -82,14 +83,19 @@ class DropZoneProvider extends Component { }; } + onMouseUp() { + this.timeoutId = window.setTimeout( this.resetDragState ); + } + componentDidMount() { window.addEventListener( 'dragover', this.onDragOver ); - window.addEventListener( 'mouseup', this.resetDragState ); + window.addEventListener( 'mouseup', this.onMouseUp ); } componentWillUnmount() { window.removeEventListener( 'dragover', this.onDragOver ); - window.removeEventListener( 'mouseup', this.resetDragState ); + window.removeEventListener( 'mouseup', this.onMouseUp ); + window.clearTimeout( this.timeoutId ); } addDropZone( dropZone ) { From d5503d2656292e13571d7759add9a3d22b65d378 Mon Sep 17 00:00:00 2001 From: Ella van Durpe Date: Fri, 26 Jun 2020 15:12:47 +0300 Subject: [PATCH 2/2] Extract logic --- .../components/block-list/block-wrapper.js | 139 +---------------- .../block-list/drag-on-long-press.js | 145 ++++++++++++++++++ 2 files changed, 152 insertions(+), 132 deletions(-) create mode 100644 packages/block-editor/src/components/block-list/drag-on-long-press.js diff --git a/packages/block-editor/src/components/block-list/block-wrapper.js b/packages/block-editor/src/components/block-list/block-wrapper.js index 9d50b82f0d760..a4b82ee3a7607 100644 --- a/packages/block-editor/src/components/block-list/block-wrapper.js +++ b/packages/block-editor/src/components/block-list/block-wrapper.js @@ -13,7 +13,6 @@ import { useState, useContext, forwardRef, - createPortal, } from '@wordpress/element'; import { focus, isTextField, placeCaretAtHorizontalEdge } from '@wordpress/dom'; import { BACKSPACE, DELETE, ENTER } from '@wordpress/keycodes'; @@ -27,28 +26,8 @@ import { isInsideRootBlock } from '../../utils/dom'; import useMovingAnimation from '../use-moving-animation'; import { Context, SetBlockNodes } from './root-container'; import { BlockListBlockContext } from './block'; +import { DragOnLongPress } from './drag-on-long-press'; import ELEMENTS from './block-wrapper-elements'; -import BlockDraggableChip from '../block-draggable/draggable-chip'; - -function useOnLongPress( ref, timeout, callback, deps ) { - useEffect( () => { - let timeoutId; - const set = ( event ) => { - clearTimeout( timeoutId ); - timeoutId = setTimeout( () => callback( event ), timeout ); - }; - const unset = () => { - clearTimeout( timeoutId ); - }; - ref.current.addEventListener( 'mousedown', set ); - ref.current.addEventListener( 'mouseup', unset ); - return () => { - ref.current.removeEventListener( 'mousedown', set ); - ref.current.removeEventListener( 'mouseup', unset ); - clearTimeout( timeoutId ); - }; - }, deps ); -} const BlockComponent = forwardRef( ( @@ -257,111 +236,6 @@ const BlockComponent = forwardRef( setHovered( false ); } - const [ isDraggging, setIsDragging ] = useState( false ); - const container = useRef( document.createElement( 'div' ) ); - - useOnLongPress( - wrapper, - 250, - () => { - if ( - // isSelected is oudated. - ! wrapper.current.classList.contains( 'is-selected' ) || - ! window.getSelection().isCollapsed - ) { - return; - } - - const cancel = () => { - window.removeEventListener( 'mouseup', cancel ); - document.removeEventListener( 'selectionchange', cancel ); - - wrapper.current.style.transform = ''; - wrapper.current.style.transition = ''; - }; - - window.addEventListener( 'mouseup', cancel ); - document.addEventListener( 'selectionchange', cancel ); - - wrapper.current.style.transform = 'scale(1.02)'; - wrapper.current.style.transition = 'transform .75s ease-in-out'; - }, - [] - ); - - useOnLongPress( - wrapper, - 1000, - ( _event ) => { - wrapper.current.style.transform = ''; - wrapper.current.style.transition = ''; - - if ( - ! wrapper.current.classList.contains( 'is-selected' ) || - ! window.getSelection().isCollapsed - ) { - return; - } - - const { parentNode } = wrapper.current; - - setIsDragging( true ); - wrapper.current.style.display = 'none'; - window.getSelection().removeAllRanges(); - parentNode.appendChild( container.current ); - container.current.style.position = 'fixed'; - container.current.style.pointerEvents = 'none'; - container.current.style.left = _event.clientX - 20 + 'px'; - container.current.style.top = _event.clientY + 20 + 'px'; - - const onMouseMove = ( event ) => { - const newEvent = new window.CustomEvent( 'dragover', { - bubbles: true, - detail: { - clientX: event.clientX, - clientY: event.clientY, - }, - } ); - window.dispatchEvent( newEvent ); - - container.current.style.left = event.clientX - 20 + 'px'; - container.current.style.top = event.clientY + 20 + 'px'; - }; - - const onMouseUp = ( event ) => { - window.removeEventListener( 'mousemove', onMouseMove ); - window.removeEventListener( 'mouseup', onMouseUp ); - - setIsDragging( false ); - - const dataTransfer = new window.DataTransfer(); - const data = { - type: 'block', - srcIndex: index, - srcClientId: clientId, - srcRootClientId: rootClientId || '', - }; - - dataTransfer.setData( 'text', JSON.stringify( data ) ); - - const newEvent = new window.DragEvent( 'drop', { - bubbles: true, - dataTransfer, - } ); - event.target.dispatchEvent( newEvent ); - parentNode.removeChild( container.current ); - - if ( wrapper.current ) { - wrapper.current.style.display = ''; - } - }; - - window.addEventListener( 'mousemove', onMouseMove ); - window.addEventListener( 'mouseup', onMouseUp ); - }, - [] - ); - return ( // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events { children } - { isDraggging && - createPortal( - , - container.current - ) } + ); } diff --git a/packages/block-editor/src/components/block-list/drag-on-long-press.js b/packages/block-editor/src/components/block-list/drag-on-long-press.js new file mode 100644 index 0000000000000..6de265d1775e2 --- /dev/null +++ b/packages/block-editor/src/components/block-list/drag-on-long-press.js @@ -0,0 +1,145 @@ +/** + * WordPress dependencies + */ +import { useRef, useEffect, useState, createPortal } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import BlockDraggableChip from '../block-draggable/draggable-chip'; + +function useOnLongPress( ref, timeout, callback, deps ) { + useEffect( () => { + let timeoutId; + const set = ( event ) => { + clearTimeout( timeoutId ); + timeoutId = setTimeout( () => callback( event ), timeout ); + }; + const unset = () => { + clearTimeout( timeoutId ); + }; + ref.current.addEventListener( 'mousedown', set ); + ref.current.addEventListener( 'mouseup', unset ); + return () => { + ref.current.removeEventListener( 'mousedown', set ); + ref.current.removeEventListener( 'mouseup', unset ); + clearTimeout( timeoutId ); + }; + }, deps ); +} + +export function DragOnLongPress( { target, index, clientId, rootClientId } ) { + const [ isDraggging, setIsDragging ] = useState( false ); + const container = useRef( document.createElement( 'div' ) ); + + useOnLongPress( + target, + 250, + () => { + if ( + // isSelected is oudated. + ! target.current.classList.contains( 'is-selected' ) || + ! window.getSelection().isCollapsed + ) { + return; + } + + const cancel = () => { + window.removeEventListener( 'mouseup', cancel ); + document.removeEventListener( 'selectionchange', cancel ); + + target.current.style.transform = ''; + target.current.style.transition = ''; + }; + + window.addEventListener( 'mouseup', cancel ); + document.addEventListener( 'selectionchange', cancel ); + + target.current.style.transform = 'scale(1.02)'; + target.current.style.transition = 'transform .75s ease-in-out'; + }, + [] + ); + + useOnLongPress( + target, + 1000, + ( _event ) => { + target.current.style.transform = ''; + target.current.style.transition = ''; + + if ( + ! target.current.classList.contains( 'is-selected' ) || + ! window.getSelection().isCollapsed + ) { + return; + } + + const { parentNode } = target.current; + + setIsDragging( true ); + target.current.style.display = 'none'; + window.getSelection().removeAllRanges(); + parentNode.appendChild( container.current ); + container.current.style.position = 'fixed'; + container.current.style.pointerEvents = 'none'; + container.current.style.left = _event.clientX - 20 + 'px'; + container.current.style.top = _event.clientY + 20 + 'px'; + + const onMouseMove = ( event ) => { + const newEvent = new window.CustomEvent( 'dragover', { + bubbles: true, + detail: { + clientX: event.clientX, + clientY: event.clientY, + }, + } ); + window.dispatchEvent( newEvent ); + + container.current.style.left = event.clientX - 20 + 'px'; + container.current.style.top = event.clientY + 20 + 'px'; + }; + + const onMouseUp = ( event ) => { + window.removeEventListener( 'mousemove', onMouseMove ); + window.removeEventListener( 'mouseup', onMouseUp ); + + setIsDragging( false ); + + const dataTransfer = new window.DataTransfer(); + const data = { + type: 'block', + srcIndex: index, + srcClientId: clientId, + srcRootClientId: rootClientId || '', + }; + + dataTransfer.setData( 'text', JSON.stringify( data ) ); + + const newEvent = new window.DragEvent( 'drop', { + bubbles: true, + dataTransfer, + } ); + event.target.dispatchEvent( newEvent ); + parentNode.removeChild( container.current ); + + if ( target.current ) { + target.current.style.display = ''; + } + }; + + window.addEventListener( 'mousemove', onMouseMove ); + window.addEventListener( 'mouseup', onMouseUp ); + }, + [] + ); + + if ( ! isDraggging ) { + return null; + } + + return createPortal( + , + container.current + ); +}