From 84a95693b1743f43271046d02081b73a86557fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Wed, 25 Nov 2020 23:30:30 +0200 Subject: [PATCH] Drop zone provider: option to avoid wrapper element --- packages/components/src/drop-zone/provider.js | 230 +++++++++--------- packages/components/src/index.js | 6 +- .../edit-post/src/components/layout/index.js | 2 + packages/edit-post/src/editor.js | 6 +- 4 files changed, 125 insertions(+), 119 deletions(-) diff --git a/packages/components/src/drop-zone/provider.js b/packages/components/src/drop-zone/provider.js index 80faacf92102cf..323801c647866f 100644 --- a/packages/components/src/drop-zone/provider.js +++ b/packages/components/src/drop-zone/provider.js @@ -1,18 +1,17 @@ /** * External dependencies */ -import { find, some, filter, includes } from 'lodash'; +import { find, some, filter, includes, throttle } from 'lodash'; /** * WordPress dependencies */ import { createContext, - useCallback, useEffect, useRef, + useContext, } from '@wordpress/element'; -import { useThrottle } from '@wordpress/compose'; import { getFilesFromDataTransfer } from '@wordpress/dom'; import isShallowEqual from '@wordpress/is-shallow-equal'; @@ -74,7 +73,7 @@ function getPosition( event ) { function getHoveredDropZone( dropZones, position, dragEventType ) { const hoveredDropZones = filter( - Array.from( dropZones.current ), + Array.from( dropZones ), ( dropZone ) => isTypeSupportedByDropZone( dragEventType, dropZone ) && isWithinElementBounds( @@ -107,126 +106,121 @@ export const INITIAL_DROP_ZONE_STATE = { type: null, }; -export default function DropZoneProvider( { children } ) { - const ref = useRef(); - const dropZones = useRef( new Set( [] ) ); - const lastRelative = useRef(); - - const updateDragZones = useCallback( ( event ) => { - if ( - lastRelative.current && - lastRelative.current.contains( event.target ) - ) { - return; - } +export function useDrop( ref ) { + const dropZones = useContext( Context ); - const dragEventType = getDragEventType( event ); - const position = getPosition( event ); - const hoveredDropZone = getHoveredDropZone( - dropZones, - position, - dragEventType - ); + useEffect( () => { + const { ownerDocument } = ref.current; + const { defaultView } = ownerDocument; - if ( hoveredDropZone && hoveredDropZone.isRelative ) { - lastRelative.current = hoveredDropZone.element.current.offsetParent; - } else { - lastRelative.current = null; - } + let lastRelative; - // Notifying the dropzones - dropZones.current.forEach( ( dropZone ) => { - const isDraggingOverDropZone = dropZone === hoveredDropZone; - const newState = { - isDraggingOverDocument: isTypeSupportedByDropZone( - dragEventType, - dropZone - ), - isDraggingOverElement: isDraggingOverDropZone, - x: - isDraggingOverDropZone && dropZone.withPosition - ? position.x - : null, - y: - isDraggingOverDropZone && dropZone.withPosition - ? position.y - : null, - type: isDraggingOverDropZone ? dragEventType : null, - }; - - dropZone.setState( ( state ) => { - if ( isShallowEqual( state, newState ) ) { - return state; - } + function updateDragZones( event ) { + if ( lastRelative && lastRelative.contains( event.target ) ) { + return; + } + + const dragEventType = getDragEventType( event ); + const position = getPosition( event ); + const hoveredDropZone = getHoveredDropZone( + dropZones, + position, + dragEventType + ); + + if ( hoveredDropZone && hoveredDropZone.isRelative ) { + lastRelative = hoveredDropZone.element.current.offsetParent; + } else { + lastRelative = null; + } - return newState; + // Notifying the dropzones + dropZones.forEach( ( dropZone ) => { + const isDraggingOverDropZone = dropZone === hoveredDropZone; + const newState = { + isDraggingOverDocument: isTypeSupportedByDropZone( + dragEventType, + dropZone + ), + isDraggingOverElement: isDraggingOverDropZone, + x: + isDraggingOverDropZone && dropZone.withPosition + ? position.x + : null, + y: + isDraggingOverDropZone && dropZone.withPosition + ? position.y + : null, + type: isDraggingOverDropZone ? dragEventType : null, + }; + + dropZone.setState( ( state ) => { + if ( isShallowEqual( state, newState ) ) { + return state; + } + + return newState; + } ); } ); - } ); - event.preventDefault(); - }, [] ); + event.preventDefault(); + } - const throttledUpdateDragZones = useThrottle( updateDragZones, 200 ); + const throttledUpdateDragZones = throttle( updateDragZones, 200 ); - const onDragOver = useCallback( - ( event ) => { + function onDragOver( event ) { throttledUpdateDragZones( event ); event.preventDefault(); - }, - [ throttledUpdateDragZones ] - ); + } - const resetDragState = useCallback( () => { - // Avoid throttled drag over handler calls - throttledUpdateDragZones.cancel(); + function resetDragState() { + // Avoid throttled drag over handler calls + throttledUpdateDragZones.cancel(); - dropZones.current.forEach( ( dropZone ) => - dropZone.setState( INITIAL_DROP_ZONE_STATE ) - ); - }, [] ); - - function onDrop( event ) { - // This seemingly useless line has been shown to resolve a Safari issue - // where files dragged directly from the dock are not recognized - event.dataTransfer && event.dataTransfer.files.length; // eslint-disable-line no-unused-expressions - - const dragEventType = getDragEventType( event ); - const position = getPosition( event ); - const hoveredDropZone = getHoveredDropZone( - dropZones, - position, - dragEventType - ); - - resetDragState(); - - if ( hoveredDropZone ) { - switch ( dragEventType ) { - case 'file': - hoveredDropZone.onFilesDrop( - getFilesFromDataTransfer( event.dataTransfer ), - position - ); - break; - case 'html': - hoveredDropZone.onHTMLDrop( - event.dataTransfer.getData( 'text/html' ), - position - ); - break; - case 'default': - hoveredDropZone.onDrop( event, position ); - } + dropZones.forEach( ( dropZone ) => + dropZone.setState( INITIAL_DROP_ZONE_STATE ) + ); } - event.stopPropagation(); - event.preventDefault(); - } + function onDrop( event ) { + // This seemingly useless line has been shown to resolve a Safari issue + // where files dragged directly from the dock are not recognized + event.dataTransfer && event.dataTransfer.files.length; // eslint-disable-line no-unused-expressions + + const dragEventType = getDragEventType( event ); + const position = getPosition( event ); + const hoveredDropZone = getHoveredDropZone( + dropZones, + position, + dragEventType + ); + + resetDragState(); + + if ( hoveredDropZone ) { + switch ( dragEventType ) { + case 'file': + hoveredDropZone.onFilesDrop( + getFilesFromDataTransfer( event.dataTransfer ), + position + ); + break; + case 'html': + hoveredDropZone.onHTMLDrop( + event.dataTransfer.getData( 'text/html' ), + position + ); + break; + case 'default': + hoveredDropZone.onDrop( event, position ); + } + } - useEffect( () => { - const { ownerDocument } = ref.current; - const { defaultView } = ownerDocument; + event.stopPropagation(); + event.preventDefault(); + } + defaultView.addEventListener( 'drop', onDrop ); defaultView.addEventListener( 'dragover', onDragOver ); defaultView.addEventListener( 'mouseup', resetDragState ); // Note that `dragend` doesn't fire consistently for file and HTML drag @@ -235,19 +229,25 @@ export default function DropZoneProvider( { children } ) { defaultView.addEventListener( 'dragend', resetDragState ); return () => { + defaultView.removeEventListener( 'drop', onDrop ); defaultView.removeEventListener( 'dragover', onDragOver ); defaultView.removeEventListener( 'mouseup', resetDragState ); defaultView.removeEventListener( 'dragend', resetDragState ); }; - }, [ onDragOver, resetDragState ] ); + }, [ ref, dropZones ] ); +} +export function DropZoneContextProvider( props ) { + const ref = useRef( new Set( [] ) ); + return ; +} + +export default function DropZoneProvider( props ) { + const ref = useRef(); + useDrop( ref ); return ( -
- { children } +
+
); } diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 696659ec71103b..e499b06b096831 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -45,7 +45,11 @@ export { default as DropZone, useDropZone as __unstableUseDropZone, } from './drop-zone'; -export { default as DropZoneProvider } from './drop-zone/provider'; +export { + default as DropZoneProvider, + DropZoneContextProvider as __unstableDropZoneContextProvider, + useDrop as __unstableUseDrop, +} from './drop-zone/provider'; export { default as Dropdown } from './dropdown'; export { default as DropdownMenu } from './dropdown-menu'; export { default as ExternalLink } from './external-link'; diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 9200c56812022c..d427962900730c 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -24,6 +24,7 @@ import { ScrollLock, Popover, FocusReturnProvider, + __unstableUseDrop as useDrop, } from '@wordpress/components'; import { useViewportMatch } from '@wordpress/compose'; import { PluginArea } from '@wordpress/plugins'; @@ -166,6 +167,7 @@ function Layout( { settings } ) { ); const ref = useRef(); + useDrop( ref ); useEditorStyles( ref, settings.styles ); return ( diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index 69fab90c77b5a6..feeb88c109f71f 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -17,7 +17,7 @@ import { StrictMode, Component } from '@wordpress/element'; import { KeyboardShortcuts, SlotFillProvider, - DropZoneProvider, + __unstableDropZoneContextProvider as DropZoneContextProvider, } from '@wordpress/components'; import { compose } from '@wordpress/compose'; @@ -136,7 +136,7 @@ class Editor extends Component { - + - +