I think the key here is that the Field component can be either a DragSource or a DropTarget . Then we can define a standard set of drop types that will affect how the state is mutated.
const DropType = { After: 'DROP_AFTER', Before: 'DROP_BEFORE', Inside: 'DROP_INSIDE' };
After and Before will allow you to reorder fields, and Inside allows nesting of fields (or discarding in a workbench).
Now the creator of the action for processing any drop will be:
const drop = (source, target, dropType) => ({ type: actions.DROP, source, target, dropType });
It simply accepts the source and target objects, as well as the type of drop, which will then be translated into a state mutation.
A drag type is simply a function of the target borders, the drag position and (optionally) the drag source, all in the context of a specific DropTarget type:
(bounds, position, source) => dropType
This function must be defined for each supported DropTarget type. This will allow each DropTarget support a different set of drop types. For example, Workbench knows how to drop something inside itself, and not earlier or later, so the implementation for a workbench might look like this:
(bounds, position) => DropType.Inside
For a Field you can use the logic from the Example of sorting simple maps , where the upper half of DropTarget translates to Before drop and the lower half to After drop:
(bounds, position) => { const middleY = (bounds.bottom - bounds.top) / 2; const relativeY = position.y - bounds.top; return relativeY < middleY ? DropType.Before : DropType.After; };
This approach also means that every DropTarget can handle the spec drop() method in the same way:
- get the bounds of the target element's DOM element
- get quotation mark position
- calculate fall type from borders, position and source
- If any type of reset occurs, handle the frame action
With React DnD, we need to be careful to properly handle nested drop targets, since we have Field in the Workbench :
const configureDrop = getDropType => (props, monitor, component) => {
The Component class will look something like this:
@connect(...) @DragSource(ItemTypes.FIELD, { beginDrag: ({ unique, parent, attributes }) => ({ unique, parent, attributes }) }, dragCollect)
A couple of notes:
Remember the order of the decorator. DropTarget should wrap the component, then DragSource should wrap the wrapped component. Thus, we have access to the correct instance of Component inside drop() .
The final root of the node abbreviation should be the native element of the node, not the user component of node.
Any component that will be embellished with DropTarget using configureDrop() will require the component to set its node DOM ref root to the node property.
Since we are handling drop in DropTarget , DragSource just needs to implement the beginDrag() method, which simply returns any state you want to mix to the state of your application.
The last thing to do is process each drop type in your reducer. It is important to remember that every time you move something, you need to remove the source from the current parent (if applicable), and then insert it into the new parent. Each action can change the state of up to three elements, the source parent (clear its children ), the source (assign it a parent link) and the target parent or target if Inside drop (add to its children ).
You may also need to make your state an object instead of an array, which might be easier to work with when implementing a gearbox.
{ AWJOPD: { ... }, DAWPNC: { ... }, workbench: { key: 'workbench', parent: null, children: [ 'DAWPNC' ] } }