Skip to content

Commit

Permalink
Drop zone provider: option to avoid wrapper element
Browse files Browse the repository at this point in the history
  • Loading branch information
ellatrix committed Nov 25, 2020
1 parent b18fc28 commit 84a9569
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 119 deletions.
230 changes: 115 additions & 115 deletions packages/components/src/drop-zone/provider.js
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -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 <Provider { ...props } value={ ref.current } />;
}

export default function DropZoneProvider( props ) {
const ref = useRef();
useDrop( ref );
return (
<div
ref={ ref }
onDrop={ onDrop }
className="components-drop-zone__provider"
>
<Provider value={ dropZones.current }>{ children }</Provider>
<div ref={ ref } className="components-drop-zone__provider">
<DropZoneContextProvider { ...props } />
</div>
);
}
6 changes: 5 additions & 1 deletion packages/components/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 2 additions & 0 deletions packages/edit-post/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
ScrollLock,
Popover,
FocusReturnProvider,
__unstableUseDrop as useDrop,
} from '@wordpress/components';
import { useViewportMatch } from '@wordpress/compose';
import { PluginArea } from '@wordpress/plugins';
Expand Down Expand Up @@ -166,6 +167,7 @@ function Layout( { settings } ) {
);
const ref = useRef();

useDrop( ref );
useEditorStyles( ref, settings.styles );

return (
Expand Down
6 changes: 3 additions & 3 deletions packages/edit-post/src/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -136,7 +136,7 @@ class Editor extends Component {
<StrictMode>
<EditPostSettings.Provider value={ settings }>
<SlotFillProvider>
<DropZoneProvider>
<DropZoneContextProvider>
<EditorProvider
settings={ editorSettings }
post={ post }
Expand All @@ -153,7 +153,7 @@ class Editor extends Component {
</ErrorBoundary>
<PostLockedModal />
</EditorProvider>
</DropZoneProvider>
</DropZoneContextProvider>
</SlotFillProvider>
</EditPostSettings.Provider>
</StrictMode>
Expand Down

0 comments on commit 84a9569

Please sign in to comment.