Skip to content

Commit

Permalink
Add API for effectsAllowed and getDropEffect with sane defaults
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon committed Oct 31, 2014
1 parent e213023 commit b80ea00
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 46 deletions.
10 changes: 6 additions & 4 deletions modules/actions/DragDropActionCreators.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ var DragDropDispatcher = require('../dispatcher/DragDropDispatcher'),
DragDropActionTypes = require('../constants/DragDropActionTypes');

var DragDropActionCreators = {
startDragging(itemType, item) {
startDragging(itemType, item, effectsAllowed) {
DragDropDispatcher.handleAction({
type: DragDropActionTypes.DRAG_START,
itemType: itemType,
item: item
item: item,
effectsAllowed: effectsAllowed
});
},

recordDrop() {
recordDrop(dropEffect) {
DragDropDispatcher.handleAction({
type: DragDropActionTypes.DROP
type: DragDropActionTypes.DROP,
dropEffect: dropEffect
});
},

Expand Down
3 changes: 1 addition & 2 deletions modules/constants/DropEffects.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
var DropEffects = {
COPY: 'copy',
MOVE: 'move',
LINK: 'link',
NONE: 'none'
LINK: 'link'
};

module.exports = DropEffects;
88 changes: 60 additions & 28 deletions modules/mixins/DragDropMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var DragDropActionCreators = require('../actions/DragDropActionCreators'),
NativeDragDropSupport = require('../utils/NativeDragDropSupport'),
EnterLeaveMonitor = require('../utils/EnterLeaveMonitor'),
MemoizeBindMixin = require('./MemoizeBindMixin'),
DropEffects = require('../constants/DropEffects'),
configureDataTransfer = require('../utils/configureDataTransfer'),
isFileDragDropEvent = require('../utils/isFileDragDropEvent'),
bindAll = require('../utils/bindAll'),
Expand All @@ -13,6 +14,7 @@ var DragDropActionCreators = require('../actions/DragDropActionCreators'),
defaults = require('lodash-node/modern/objects/defaults'),
union = require('lodash-node/modern/arrays/union'),
without = require('lodash-node/modern/arrays/without'),
isArray = require('lodash-node/modern/objects/isArray'),
isObject = require('lodash-node/modern/objects/isObject'),
noop = require('lodash-node/modern/utilities/noop');

Expand Down Expand Up @@ -75,6 +77,10 @@ var DefaultDropTarget = {
return true;
},

getDropEffect(allowedEffects) {
return allowedEffects[0];
},

enter: noop,
over: noop,
leave: noop,
Expand All @@ -90,7 +96,7 @@ var DragDropMixin = {
getInitialState() {
var state = {
ownDraggedItemType: null,
hasDragEntered: false
currentDropEffect: null
};

return merge(state, this.getStateFromDragDropStore());
Expand Down Expand Up @@ -137,11 +143,11 @@ var DragDropMixin = {
checkDropTargetDefined(this, type);

var isDragging = this.getActiveDropTargetType() === type,
hasDragEntered = this.state.hasDragEntered;
isHovering = !!this.state.currentDropEffect;

return {
isDragging: isDragging,
isHovering: isDragging && hasDragEntered
isHovering: isDragging && isHovering
};
},

Expand Down Expand Up @@ -191,7 +197,9 @@ var DragDropMixin = {
},

handleDragDropStoreChange() {
this.setState(this.getStateFromDragDropStore());
if (this.isMounted()) {
this.setState(this.getStateFromDragDropStore());
}
},

dragSourceFor(type) {
Expand Down Expand Up @@ -221,12 +229,17 @@ var DragDropMixin = {
);

var dragOptions = beginDrag(e),
{ item } = dragOptions;
{ item, dragPreview, dragAnchors, effectsAllowed } = dragOptions;

if (!effectsAllowed) {
effectsAllowed = [DropEffects.MOVE];
}

configureDataTransfer(this.getDOMNode(), e.nativeEvent, dragOptions);
invariant(isArray(effectsAllowed) && effectsAllowed.length > 0, 'Expected effectsAllowed to be non-empty array');
invariant(isObject(item), 'Expected return value of beginDrag to contain "item" object');

DragDropActionCreators.startDragging(type, item);
configureDataTransfer(this.getDOMNode(), e.nativeEvent, dragPreview, dragAnchors, effectsAllowed);
DragDropActionCreators.startDragging(type, item, effectsAllowed);

// Delay setting own state by a tick so `getDragState(type).isDragging`
// doesn't return `true` yet. Otherwise browser will capture dragged state
Expand All @@ -245,7 +258,7 @@ var DragDropMixin = {
NativeDragDropSupport.handleDragEnd();

var { endDrag } = this._dragSources[type],
didDrop = DragDropStore.didDrop();
recordedDropEffect = DragDropStore.getDropEffect();

DragDropActionCreators.endDragging();

Expand All @@ -261,7 +274,7 @@ var DragDropMixin = {
ownDraggedItemType: null
});

endDrag(didDrop, e);
endDrag(recordedDropEffect, e);
},

dropTargetFor(...types) {
Expand All @@ -278,17 +291,6 @@ var DragDropMixin = {
};
},

handleDragOver(types, e) {
if (!this.isAnyDropTargetActive(types)) {
return;
}

e.preventDefault();

var { over } = this._dropTargets[this.state.draggedItemType];
over(this.state.draggedItem, e);
},

handleDragEnter(types, e) {
if (!this.isAnyDropTargetActive(types)) {
return;
Expand All @@ -298,14 +300,40 @@ var DragDropMixin = {
return;
}

var { enter, getDropEffect } = this._dropTargets[this.state.draggedItemType],
effectsAllowed = DragDropStore.getEffectsAllowed(),
dropEffect = getDropEffect(effectsAllowed);

if (dropEffect && !isFileDragDropEvent(e)) {
invariant(
effectsAllowed.indexOf(dropEffect) > -1,
'Effect %s supplied by drop target is not one of the effects allowed by drag source: %s',
dropEffect,
effectsAllowed.join(', ')
);
}

this.setState({
hasDragEntered: true
currentDropEffect: dropEffect
});

var { enter } = this._dropTargets[this.state.draggedItemType];
enter(this.state.draggedItem, e);
},

handleDragOver(types, e) {
if (!this.isAnyDropTargetActive(types)) {
return;
}

e.preventDefault();

var { over, getDropEffect } = this._dropTargets[this.state.draggedItemType];
over(this.state.draggedItem, e);

// Don't use `none` because this will prevent browser from firing `dragend`
NativeDragDropSupport.handleDragOver(e, this.state.currentDropEffect || 'move');
},

handleDragLeave(types, e) {
if (!this.isAnyDropTargetActive(types)) {
return;
Expand All @@ -316,7 +344,7 @@ var DragDropMixin = {
}

this.setState({
hasDragEntered: false
currentDropEffect: null
});

var { leave } = this._dropTargets[this.state.draggedItemType];
Expand All @@ -331,7 +359,9 @@ var DragDropMixin = {
e.preventDefault();

var item = this.state.draggedItem,
{ acceptDrop } = this._dropTargets[this.state.draggedItemType];
{ acceptDrop } = this._dropTargets[this.state.draggedItemType],
{ currentDropEffect } = this.state,
recordedDropEffect = DragDropStore.getDropEffect();

if (isFileDragDropEvent(e)) {
// We don't know file list until the `drop` event,
Expand All @@ -343,13 +373,15 @@ var DragDropMixin = {

this._monitor.reset();

if (!recordedDropEffect && currentDropEffect) {
DragDropActionCreators.recordDrop(currentDropEffect);
}

this.setState({
hasDragEntered: false
currentDropEffect: null
});

if (acceptDrop(item, e) !== false) {
DragDropActionCreators.recordDrop();
}
acceptDrop(item, e, recordedDropEffect);
}
};

Expand Down
19 changes: 13 additions & 6 deletions modules/stores/DragDropStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@ var DragDropDispatcher = require('../dispatcher/DragDropDispatcher'),

var _draggedItem = null,
_draggedItemType = null,
_didDrop = false;
_effectsAllowed = null,
_dropEffect = null;

var DragDropStore = createStore({
isDragging() {
return !!_draggedItem;
},

didDrop() {
return _didDrop;
getEffectsAllowed() {
return _effectsAllowed;
},

getDropEffect() {
return _dropEffect;
},

getDraggedItem() {
Expand All @@ -31,21 +36,23 @@ DragDropDispatcher.register(function (payload) {

switch (action.type) {
case DragDropActionTypes.DRAG_START:
_didDrop = false;
_dropEffect = null;
_draggedItem = action.item;
_draggedItemType = action.itemType;
_effectsAllowed = action.effectsAllowed;
DragDropStore.emitChange();
break;

case DragDropActionTypes.DROP:
_didDrop = true;
_dropEffect = action.dropEffect;
DragDropStore.emitChange();
break;

case DragDropActionTypes.DRAG_END:
_didDrop = false;
_draggedItem = null;
_draggedItemType = null;
_effectsAllowed = null;
_dropEffect = null;
DragDropStore.emitChange();
break;
}
Expand Down
15 changes: 14 additions & 1 deletion modules/utils/NativeDragDropSupport.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

var DragDropActionCreators = require('../actions/DragDropActionCreators'),
NativeDragItemTypes = require('../constants/NativeDragItemTypes'),
DropEffects = require('../constants/DropEffects'),
EnterLeaveMonitor = require('../utils/EnterLeaveMonitor'),
isFileDragDropEvent = require('./isFileDragDropEvent'),
shallowEqual = require('react/lib/shallowEqual'),
Expand All @@ -16,7 +17,8 @@ var _monitor = new EnterLeaveMonitor(),
_initialDragTargetRect,
_imitateCurrentDragEnd,
_dragTargetRectDidChange,
_lastDragSourceCheckTimeout;
_lastDragSourceCheckTimeout,
_currentDropEffect;

function getElementRect(el) {
var rect = el.getBoundingClientRect();
Expand Down Expand Up @@ -52,6 +54,10 @@ if (typeof window !== 'undefined') {
});

window.addEventListener('dragover', function (e) {
// At the top level of event bubbling, use previously set drop effect and reset it.
e.dataTransfer.dropEffect = _currentDropEffect;
_currentDropEffect = null;

if (!_currentDragTarget) {
return;
}
Expand Down Expand Up @@ -106,6 +112,13 @@ var NativeDragDropSupport = {
_initialDragTargetRect = null;
_dragTargetRectDidChange = false;
_imitateCurrentDragEnd = null;
},

handleDragOver(e, dropEffect) {
// As event bubbles top-down, first specified effect will be used
if (!_currentDropEffect) {
_currentDropEffect = dropEffect;
}
}
};

Expand Down
10 changes: 5 additions & 5 deletions modules/utils/configureDataTransfer.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use strict';

var shouldUseDragPreview = require('./shouldUseDragPreview'),
getDragImageOffset = require('./getDragImageOffset');
getDragImageOffset = require('./getDragImageOffset'),
getBrowserEffectAllowed = require('./getBrowserEffectAllowed');

function configureDataTransfer(containerNode, nativeEvent, dragOptions) {
var { dataTransfer } = nativeEvent,
{ dragPreview, effectAllowed, dragAnchors } = dragOptions;
function configureDataTransfer(containerNode, nativeEvent, dragPreview, dragAnchors, effectsAllowed) {
var { dataTransfer } = nativeEvent;

try {
// Firefox won't drag without setting data
Expand All @@ -19,7 +19,7 @@ function configureDataTransfer(containerNode, nativeEvent, dragOptions) {
dataTransfer.setDragImage(dragPreview, dragOffset.x, dragOffset.y);
}

dataTransfer.effectAllowed = effectAllowed;
dataTransfer.effectAllowed = getBrowserEffectAllowed(effectsAllowed);
}

module.exports = configureDataTransfer;
29 changes: 29 additions & 0 deletions modules/utils/getBrowserEffectAllowed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

var DropEffects = require('../constants/DropEffects');

function getBrowserEffectAllowed(effectsAllowed) {
var allowCopy = effectsAllowed.indexOf(DropEffects.COPY) > -1,
allowMove = effectsAllowed.indexOf(DropEffects.MOVE) > -1,
allowLink = effectsAllowed.indexOf(DropEffects.LINK) > -1;

if (allowCopy && allowMove && allowLink) {
return 'all';
} else if (allowCopy && allowMove) {
return 'copyMove';
} else if (allowLink && allowMove) {
return 'linkMove';
} else if (allowCopy && allowLink) {
return 'copyLink';
} else if (allowCopy) {
return 'copy';
} else if (allowMove) {
return 'move';
} else if (allowLink) {
return 'link';
} else {
return 'none';
}
}

module.exports = getBrowserEffectAllowed;

0 comments on commit b80ea00

Please sign in to comment.