Skip to content

Commit

Permalink
Make alt drag work for drop sources that can take either copy or move…
Browse files Browse the repository at this point in the history
… drag operations (#675)

* Default dropEffect to 'copy' when altKey is pressed

* Pass dropEffect as options to dropResult

* Add example showing copy and move drag operations

* Better prop naming

* Initialize this.altKeyPressed in constructor
  • Loading branch information
acusti authored and darthtrevino committed Mar 14, 2017
1 parent 9e062eb commit 1573fe9
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 4 deletions.
63 changes: 63 additions & 0 deletions examples/01 Dustbin/Copy or Move/Box.js
@@ -0,0 +1,63 @@
import React, { Component, PropTypes } from 'react';
import { DragSource } from 'react-dnd';
import ItemTypes from '../Single Target/ItemTypes';

const style = {
border: '1px dashed gray',
backgroundColor: 'white',
padding: '0.5rem 1rem',
marginRight: '1.5rem',
marginBottom: '1.5rem',
float: 'left',
};

const boxSource = {
beginDrag(props) {
return {
name: props.name,
};
},

endDrag(props, monitor) {
const item = monitor.getItem();
const dropResult = monitor.getDropResult();

if (dropResult) {
let alertMessage = '';
if (dropResult.allowedDropEffect === 'any' || dropResult.allowedDropEffect === dropResult.dropEffect) {
alertMessage = `You ${dropResult.dropEffect === 'copy' ? 'copied' : 'moved'} ${item.name} into ${dropResult.name}!`;
} else {
alertMessage = `You cannot ${dropResult.dropEffect} an item into the ${dropResult.name}`;
}
window.alert( // eslint-disable-line no-alert
alertMessage,
);
}
},
};

@DragSource(ItemTypes.BOX, boxSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging(),
}))
export default class Box extends Component {
static propTypes = {
connectDragSource: PropTypes.func.isRequired,
isDragging: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
};

render() {
const { isDragging, connectDragSource } = this.props;
const { name } = this.props;
const opacity = isDragging ? 0.4 : 1;

return (
connectDragSource(
<div style={{ ...style, opacity }}>
{name}
</div>,
)
);
}
}
26 changes: 26 additions & 0 deletions examples/01 Dustbin/Copy or Move/Container.js
@@ -0,0 +1,26 @@
import React, { Component } from 'react';
import { DragDropContextProvider } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import Dustbin from './Dustbin';
import Box from './Box';

export default class Container extends Component {
render() {
return (
<DragDropContextProvider backend={HTML5Backend}>
<div>
<div style={{ overflow: 'hidden', clear: 'both' }}>
<Dustbin allowedDropEffect="any" />
<Dustbin allowedDropEffect="copy" />
<Dustbin allowedDropEffect="move" />
</div>
<div style={{ overflow: 'hidden', clear: 'both' }}>
<Box name="Glass" />
<Box name="Banana" />
<Box name="Paper" />
</div>
</div>
</DragDropContextProvider>
);
}
}
61 changes: 61 additions & 0 deletions examples/01 Dustbin/Copy or Move/Dustbin.js
@@ -0,0 +1,61 @@
import React, { PropTypes, Component } from 'react';
import { DropTarget } from 'react-dnd';
import ItemTypes from '../Single Target/ItemTypes';

const style = {
height: '12rem',
width: '12rem',
marginRight: '1.5rem',
marginBottom: '1.5rem',
color: 'white',
padding: '1rem',
textAlign: 'center',
fontSize: '1rem',
lineHeight: 'normal',
float: 'left',
};

const boxTarget = {
drop({ allowedDropEffect }) {
return {
name: `${allowedDropEffect} Dustbin`,
allowedDropEffect,
};
},
};

@DropTarget(ItemTypes.BOX, boxTarget, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}))
export default class Dustbin extends Component {
static propTypes = {
connectDropTarget: PropTypes.func.isRequired,
isOver: PropTypes.bool.isRequired,
canDrop: PropTypes.bool.isRequired,
allowedDropEffect: PropTypes.string.isRequired,
};

render() {
const { canDrop, isOver, allowedDropEffect, connectDropTarget } = this.props;
const isActive = canDrop && isOver;

let backgroundColor = '#222';
if (isActive) {
backgroundColor = 'darkgreen';
} else if (canDrop) {
backgroundColor = 'darkkhaki';
}

return connectDropTarget(
<div style={{ ...style, backgroundColor }}>
{ `Works with ${allowedDropEffect} drop effect` }<br /><br />
{isActive ?
'Release to drop' :
'Drag a box here'
}
</div>,
);
}
}
21 changes: 21 additions & 0 deletions examples/01 Dustbin/Copy or Move/index.js
@@ -0,0 +1,21 @@
import React, { Component } from 'react';
import Container from './Container';

export default class DustbinCopyOrMove extends Component {
render() {
return (
<div>
<p>
<b><a href="https://github.com/react-dnd/react-dnd/tree/master/examples/01%20Dustbin/Copy%20or%20Move">Browse the Source</a></b>
</p>
<p>
This example demonstrates drop targets that can accept copy and move drop effects, which users can switch between by holding down or releasing the alt key as they drag.
</p>
<p>
In a todo list app, for example, the default drag and drop operation could be used to sort the list, while holding down the alt key while dragging and dropping could copy the todo item to the drop target instead of moving it.
</p>
<Container />
</div>
);
}
}
7 changes: 5 additions & 2 deletions packages/dnd-core/src/actions/dragDrop.js
Expand Up @@ -131,7 +131,7 @@ export function hover(targetIdsArg, { clientOffset = null } = {}) {
};
}

export function drop() {
export function drop(options = {}) {
const monitor = this.getMonitor();
const registry = this.getRegistry();
invariant(
Expand Down Expand Up @@ -164,7 +164,10 @@ export function drop() {

this.store.dispatch({
type: DROP,
dropResult,
dropResult: {
...options,
...dropResult,
},
});
});
}
Expand Down
9 changes: 7 additions & 2 deletions packages/react-dnd-html5-backend/src/HTML5Backend.js
Expand Up @@ -27,6 +27,7 @@ export default class HTML5Backend {
this.currentDragSourceNode = null;
this.currentDragSourceNodeOffset = null;
this.currentDragSourceNodeOffsetChanged = false;
this.altKeyPressed = false;

this.getSourceClientOffset = this.getSourceClientOffset.bind(this);
this.handleTopDragStart = this.handleTopDragStart.bind(this);
Expand Down Expand Up @@ -153,7 +154,7 @@ export default class HTML5Backend {
const sourceNodeOptions = this.sourceNodeOptions[sourceId];

return defaults(sourceNodeOptions || {}, {
dropEffect: 'move',
dropEffect: this.altKeyPressed ? 'copy' : 'move',
});
}

Expand Down Expand Up @@ -406,6 +407,8 @@ export default class HTML5Backend {
return;
}

this.altKeyPressed = e.altKey;

if (!isFirefox()) {
// Don't emit hover in `dragenter` on Firefox due to an edge case.
// If the target changes position as the result of `dragenter`, Firefox
Expand Down Expand Up @@ -447,6 +450,8 @@ export default class HTML5Backend {
return;
}

this.altKeyPressed = e.altKey;

this.actions.hover(dragOverTargetIds, {
clientOffset: getEventClientOffset(e),
});
Expand Down Expand Up @@ -509,7 +514,7 @@ export default class HTML5Backend {
this.actions.hover(dropTargetIds, {
clientOffset: getEventClientOffset(e),
});
this.actions.drop();
this.actions.drop({ dropEffect: this.getCurrentDropEffect() });

if (this.isDraggingNativeItem()) {
this.endDragNativeItem();
Expand Down
24 changes: 24 additions & 0 deletions site/Constants.js
Expand Up @@ -123,6 +123,30 @@ export const ExamplePages = [{
title: 'Stress Test'
}
}
}, {
title: 'Dustbin',
pages: {
DUSTBIN_SINGLE_TARGET: {
location: 'examples-dustbin-single-target.html',
title: 'Single Target'
},
DUSTBIN_IFRAME: {
location: 'examples-dustbin-single-target-in-iframe.html',
title: 'Within iframe'
},
DUSTBIN_COPY_OR_MOVE: {
location: 'examples-dustbin-copy-or-move.html',
title: 'Copy or Move'
},
DUSTBIN_MULTIPLE_TARGETS: {
location: 'examples-dustbin-multiple-targets.html',
title: 'Multiple Targets'
},
DUSTBIN_STRESS_TEST: {
location: 'examples-dustbin-stress-test.html',
title: 'Stress Test'
}
}
}, {
title: 'Drag Around',
pages: {
Expand Down
1 change: 1 addition & 0 deletions site/IndexPage.js
Expand Up @@ -30,6 +30,7 @@ const Examples = {
CHESSBOARD_TUTORIAL_APP: require('../examples/00 Chessboard/Tutorial App').default,
DUSTBIN_SINGLE_TARGET: require('../examples/01 Dustbin/Single Target').default,
DUSTBIN_IFRAME: require('../examples/01 Dustbin/Single Target in iframe').default,
DUSTBIN_COPY_OR_MOVE: require('../examples/01 Dustbin/Copy or Move').default,
DUSTBIN_MULTIPLE_TARGETS: require('../examples/01 Dustbin/Multiple Targets').default,
DUSTBIN_STRESS_TEST: require('../examples/01 Dustbin/Stress Test').default,
DRAG_AROUND_NAIVE: require('../examples/02 Drag Around/Naive').default,
Expand Down

0 comments on commit 1573fe9

Please sign in to comment.