qtsd-fork
Version:
Do not use this please
687 lines (591 loc) • 23.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* eslint-disable no-underscore-dangle */
var _defaults = require('lodash/defaults');
var _defaults2 = _interopRequireDefault(_defaults);
var _shallowEqual = require('./shallowEqual');
var _shallowEqual2 = _interopRequireDefault(_shallowEqual);
var _EnterLeaveCounter = require('./EnterLeaveCounter');
var _EnterLeaveCounter2 = _interopRequireDefault(_EnterLeaveCounter);
var _BrowserDetector = require('./BrowserDetector');
var _OffsetUtils = require('./OffsetUtils');
var _NativeDragSources = require('./NativeDragSources');
var _NativeTypes = require('./NativeTypes');
var NativeTypes = _interopRequireWildcard(_NativeTypes);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var HTML5Backend = function () {
function HTML5Backend(manager) {
_classCallCheck(this, HTML5Backend);
this.actions = manager.getActions();
this.monitor = manager.getMonitor();
this.registry = manager.getRegistry();
this.context = manager.getContext();
this.sourcePreviewNodes = {};
this.sourcePreviewNodeOptions = {};
this.sourceNodes = {};
this.sourceNodeOptions = {};
this.enterLeaveCounter = new _EnterLeaveCounter2.default();
this.dragStartSourceIds = [];
this.dropTargetIds = [];
this.dragEnterTargetIds = [];
this.currentNativeSource = null;
this.currentNativeHandle = null;
this.currentDragSourceNode = null;
this.currentDragSourceNodeOffset = null;
this.currentDragSourceNodeOffsetChanged = false;
this.altKeyPressed = false;
this.mouseMoveTimeoutTimer = null;
this.getSourceClientOffset = this.getSourceClientOffset.bind(this);
this.handleTopDragStart = this.handleTopDragStart.bind(this);
this.handleTopDragStartCapture = this.handleTopDragStartCapture.bind(this);
this.handleTopDragEndCapture = this.handleTopDragEndCapture.bind(this);
this.handleTopDragEnter = this.handleTopDragEnter.bind(this);
this.handleTopDragEnterCapture = this.handleTopDragEnterCapture.bind(this);
this.handleTopDragLeaveCapture = this.handleTopDragLeaveCapture.bind(this);
this.handleTopDragOver = this.handleTopDragOver.bind(this);
this.handleTopDragOverCapture = this.handleTopDragOverCapture.bind(this);
this.handleTopDrop = this.handleTopDrop.bind(this);
this.handleTopDropCapture = this.handleTopDropCapture.bind(this);
this.handleSelectStart = this.handleSelectStart.bind(this);
this.endDragIfSourceWasRemovedFromDOM = this.endDragIfSourceWasRemovedFromDOM.bind(this);
this.endDragNativeItem = this.endDragNativeItem.bind(this);
this.asyncEndDragNativeItem = this.asyncEndDragNativeItem.bind(this);
this.isNodeInDocument = this.isNodeInDocument.bind(this);
}
_createClass(HTML5Backend, [{
key: 'setup',
value: function setup() {
if (this.window === undefined) {
return;
}
if (this.window.__isReactDndBackendSetUp) {
throw new Error('Cannot have two HTML5 backends at the same time.');
}
this.window.__isReactDndBackendSetUp = true;
this.addEventListeners(this.window);
}
}, {
key: 'teardown',
value: function teardown() {
if (this.window === undefined) {
return;
}
this.window.__isReactDndBackendSetUp = false;
this.removeEventListeners(this.window);
this.clearCurrentDragSourceNode();
if (this.asyncEndDragFrameId) {
this.window.cancelAnimationFrame(this.asyncEndDragFrameId);
}
}
}, {
key: 'addEventListeners',
value: function addEventListeners(target) {
// SSR Fix (https://github.com/react-dnd/react-dnd/pull/813
if (!target.addEventListener) {
return;
}
target.addEventListener('dragstart', this.handleTopDragStart);
target.addEventListener('dragstart', this.handleTopDragStartCapture, true);
target.addEventListener('dragend', this.handleTopDragEndCapture, true);
target.addEventListener('dragenter', this.handleTopDragEnter);
target.addEventListener('dragenter', this.handleTopDragEnterCapture, true);
target.addEventListener('dragleave', this.handleTopDragLeaveCapture, true);
target.addEventListener('dragover', this.handleTopDragOver);
target.addEventListener('dragover', this.handleTopDragOverCapture, true);
target.addEventListener('drop', this.handleTopDrop);
target.addEventListener('drop', this.handleTopDropCapture, true);
}
}, {
key: 'removeEventListeners',
value: function removeEventListeners(target) {
// SSR Fix (https://github.com/react-dnd/react-dnd/pull/813
if (!target.removeEventListener) {
return;
}
target.removeEventListener('dragstart', this.handleTopDragStart);
target.removeEventListener('dragstart', this.handleTopDragStartCapture, true);
target.removeEventListener('dragend', this.handleTopDragEndCapture, true);
target.removeEventListener('dragenter', this.handleTopDragEnter);
target.removeEventListener('dragenter', this.handleTopDragEnterCapture, true);
target.removeEventListener('dragleave', this.handleTopDragLeaveCapture, true);
target.removeEventListener('dragover', this.handleTopDragOver);
target.removeEventListener('dragover', this.handleTopDragOverCapture, true);
target.removeEventListener('drop', this.handleTopDrop);
target.removeEventListener('drop', this.handleTopDropCapture, true);
}
}, {
key: 'connectDragPreview',
value: function connectDragPreview(sourceId, node, options) {
var _this = this;
this.sourcePreviewNodeOptions[sourceId] = options;
this.sourcePreviewNodes[sourceId] = node;
return function () {
delete _this.sourcePreviewNodes[sourceId];
delete _this.sourcePreviewNodeOptions[sourceId];
};
}
}, {
key: 'connectDragSource',
value: function connectDragSource(sourceId, node, options) {
var _this2 = this;
this.sourceNodes[sourceId] = node;
this.sourceNodeOptions[sourceId] = options;
var handleDragStart = function handleDragStart(e) {
return _this2.handleDragStart(e, sourceId);
};
var handleSelectStart = function handleSelectStart(e) {
return _this2.handleSelectStart(e, sourceId);
};
node.setAttribute('draggable', true);
node.addEventListener('dragstart', handleDragStart);
node.addEventListener('selectstart', handleSelectStart);
return function () {
delete _this2.sourceNodes[sourceId];
delete _this2.sourceNodeOptions[sourceId];
node.removeEventListener('dragstart', handleDragStart);
node.removeEventListener('selectstart', handleSelectStart);
node.setAttribute('draggable', false);
};
}
}, {
key: 'connectDropTarget',
value: function connectDropTarget(targetId, node) {
var _this3 = this;
var handleDragEnter = function handleDragEnter(e) {
return _this3.handleDragEnter(e, targetId);
};
var handleDragOver = function handleDragOver(e) {
return _this3.handleDragOver(e, targetId);
};
var handleDrop = function handleDrop(e) {
return _this3.handleDrop(e, targetId);
};
node.addEventListener('dragenter', handleDragEnter);
node.addEventListener('dragover', handleDragOver);
node.addEventListener('drop', handleDrop);
return function () {
node.removeEventListener('dragenter', handleDragEnter);
node.removeEventListener('dragover', handleDragOver);
node.removeEventListener('drop', handleDrop);
};
}
}, {
key: 'getCurrentSourceNodeOptions',
value: function getCurrentSourceNodeOptions() {
var sourceId = this.monitor.getSourceId();
var sourceNodeOptions = this.sourceNodeOptions[sourceId];
return (0, _defaults2.default)(sourceNodeOptions || {}, {
dropEffect: this.altKeyPressed ? 'copy' : 'move'
});
}
}, {
key: 'getCurrentDropEffect',
value: function getCurrentDropEffect() {
if (this.isDraggingNativeItem()) {
// It makes more sense to default to 'copy' for native resources
return 'copy';
}
return this.getCurrentSourceNodeOptions().dropEffect;
}
}, {
key: 'getCurrentSourcePreviewNodeOptions',
value: function getCurrentSourcePreviewNodeOptions() {
var sourceId = this.monitor.getSourceId();
var sourcePreviewNodeOptions = this.sourcePreviewNodeOptions[sourceId];
return (0, _defaults2.default)(sourcePreviewNodeOptions || {}, {
anchorX: 0.5,
anchorY: 0.5,
captureDraggingState: false
});
}
}, {
key: 'getSourceClientOffset',
value: function getSourceClientOffset(sourceId) {
return (0, _OffsetUtils.getNodeClientOffset)(this.sourceNodes[sourceId]);
}
}, {
key: 'isDraggingNativeItem',
value: function isDraggingNativeItem() {
var itemType = this.monitor.getItemType();
return Object.keys(NativeTypes).some(function (key) {
return NativeTypes[key] === itemType;
});
}
}, {
key: 'beginDragNativeItem',
value: function beginDragNativeItem(type) {
this.clearCurrentDragSourceNode();
var SourceType = (0, _NativeDragSources.createNativeDragSource)(type);
this.currentNativeSource = new SourceType();
this.currentNativeHandle = this.registry.addSource(type, this.currentNativeSource);
this.actions.beginDrag([this.currentNativeHandle]);
}
}, {
key: 'asyncEndDragNativeItem',
value: function asyncEndDragNativeItem() {
this.asyncEndDragFrameId = this.window.requestAnimationFrame(this.endDragNativeItem);
}
}, {
key: 'endDragNativeItem',
value: function endDragNativeItem() {
if (!this.isDraggingNativeItem()) {
return;
}
this.actions.endDrag();
this.registry.removeSource(this.currentNativeHandle);
this.currentNativeHandle = null;
this.currentNativeSource = null;
}
}, {
key: 'isNodeInDocument',
value: function isNodeInDocument(node) {
// Check the node either in the main document or in the current context
return document.body.contains(node) || this.window ? this.window.document.body.contains(node) : false;
}
}, {
key: 'endDragIfSourceWasRemovedFromDOM',
value: function endDragIfSourceWasRemovedFromDOM() {
var node = this.currentDragSourceNode;
if (this.isNodeInDocument(node)) {
return;
}
if (this.clearCurrentDragSourceNode()) {
this.actions.endDrag();
}
}
}, {
key: 'setCurrentDragSourceNode',
value: function setCurrentDragSourceNode(node) {
var _this4 = this;
this.clearCurrentDragSourceNode();
this.currentDragSourceNode = node;
this.currentDragSourceNodeOffset = (0, _OffsetUtils.getNodeClientOffset)(node);
this.currentDragSourceNodeOffsetChanged = false;
// A timeout of > 0 is necessary to resolve Firefox issue referenced
// See:
// * https://github.com/react-dnd/react-dnd/pull/928
// * https://github.com/react-dnd/react-dnd/issues/869
var MOUSE_MOVE_TIMEOUT = 1000;
// Receiving a mouse event in the middle of a dragging operation
// means it has ended and the drag source node disappeared from DOM,
// so the browser didn't dispatch the dragend event.
//
// We need to wait before we start listening for mousemove events.
// This is needed because the drag preview needs to be drawn or else it fires an 'mousemove' event
// immediately in some browsers.
//
// See:
// * https://github.com/react-dnd/react-dnd/pull/928
// * https://github.com/react-dnd/react-dnd/issues/869
//
this.mouseMoveTimeoutTimer = setTimeout(function () {
_this4.mouseMoveTimeoutId = null;
return _this4.window.addEventListener('mousemove', _this4.endDragIfSourceWasRemovedFromDOM, true);
}, MOUSE_MOVE_TIMEOUT);
}
}, {
key: 'clearCurrentDragSourceNode',
value: function clearCurrentDragSourceNode() {
if (this.currentDragSourceNode) {
this.currentDragSourceNode = null;
this.currentDragSourceNodeOffset = null;
this.currentDragSourceNodeOffsetChanged = false;
this.window.clearTimeout(this.mouseMoveTimeoutTimer);
this.window.removeEventListener('mousemove', this.endDragIfSourceWasRemovedFromDOM, true);
this.mouseMoveTimeoutTimer = null;
return true;
}
return false;
}
}, {
key: 'checkIfCurrentDragSourceRectChanged',
value: function checkIfCurrentDragSourceRectChanged() {
var node = this.currentDragSourceNode;
if (!node) {
return false;
}
if (this.currentDragSourceNodeOffsetChanged) {
return true;
}
this.currentDragSourceNodeOffsetChanged = !(0, _shallowEqual2.default)((0, _OffsetUtils.getNodeClientOffset)(node), this.currentDragSourceNodeOffset);
return this.currentDragSourceNodeOffsetChanged;
}
}, {
key: 'handleTopDragStartCapture',
value: function handleTopDragStartCapture() {
this.clearCurrentDragSourceNode();
this.dragStartSourceIds = [];
}
}, {
key: 'handleDragStart',
value: function handleDragStart(e, sourceId) {
this.dragStartSourceIds.unshift(sourceId);
}
}, {
key: 'handleTopDragStart',
value: function handleTopDragStart(e) {
var _this5 = this;
var dragStartSourceIds = this.dragStartSourceIds;
this.dragStartSourceIds = null;
var clientOffset = (0, _OffsetUtils.getEventClientOffset)(e);
// Avoid crashing if we missed a drop event or our previous drag died
if (this.monitor.isDragging()) {
this.actions.endDrag();
}
// Don't publish the source just yet (see why below)
this.actions.beginDrag(dragStartSourceIds, {
publishSource: false,
getSourceClientOffset: this.getSourceClientOffset,
clientOffset: clientOffset
});
var dataTransfer = e.dataTransfer;
var nativeType = (0, _NativeDragSources.matchNativeItemType)(dataTransfer);
if (this.monitor.isDragging()) {
if (typeof dataTransfer.setDragImage === 'function') {
// Use custom drag image if user specifies it.
// If child drag source refuses drag but parent agrees,
// use parent's node as drag image. Neither works in IE though.
var sourceId = this.monitor.getSourceId();
var sourceNode = this.sourceNodes[sourceId];
var dragPreview = this.sourcePreviewNodes[sourceId] || sourceNode;
var _getCurrentSourcePrev = this.getCurrentSourcePreviewNodeOptions(),
anchorX = _getCurrentSourcePrev.anchorX,
anchorY = _getCurrentSourcePrev.anchorY,
offsetX = _getCurrentSourcePrev.offsetX,
offsetY = _getCurrentSourcePrev.offsetY;
var anchorPoint = { anchorX: anchorX, anchorY: anchorY };
var offsetPoint = { offsetX: offsetX, offsetY: offsetY };
var dragPreviewOffset = (0, _OffsetUtils.getDragPreviewOffset)(sourceNode, dragPreview, clientOffset, anchorPoint, offsetPoint);
dataTransfer.setDragImage(dragPreview, dragPreviewOffset.x, dragPreviewOffset.y);
}
try {
// Firefox won't drag without setting data
dataTransfer.setData('application/json', {});
} catch (err) {}
// IE doesn't support MIME types in setData
// Store drag source node so we can check whether
// it is removed from DOM and trigger endDrag manually.
this.setCurrentDragSourceNode(e.target);
// Now we are ready to publish the drag source.. or are we not?
var _getCurrentSourcePrev2 = this.getCurrentSourcePreviewNodeOptions(),
captureDraggingState = _getCurrentSourcePrev2.captureDraggingState;
if (!captureDraggingState) {
// Usually we want to publish it in the next tick so that browser
// is able to screenshot the current (not yet dragging) state.
//
// It also neatly avoids a situation where render() returns null
// in the same tick for the source element, and browser freaks out.
setTimeout(function () {
return _this5.actions.publishDragSource();
});
} else {
// In some cases the user may want to override this behavior, e.g.
// to work around IE not supporting custom drag previews.
//
// When using a custom drag layer, the only way to prevent
// the default drag preview from drawing in IE is to screenshot
// the dragging state in which the node itself has zero opacity
// and height. In this case, though, returning null from render()
// will abruptly end the dragging, which is not obvious.
//
// This is the reason such behavior is strictly opt-in.
this.actions.publishDragSource();
}
} else if (nativeType) {
// A native item (such as URL) dragged from inside the document
this.beginDragNativeItem(nativeType);
} else if (!dataTransfer.types && (!e.target.hasAttribute || !e.target.hasAttribute('draggable'))) {
// Looks like a Safari bug: dataTransfer.types is null, but there was no draggable.
// Just let it drag. It's a native type (URL or text) and will be picked up in
// dragenter handler.
return; // eslint-disable-line no-useless-return
} else {
// If by this time no drag source reacted, tell browser not to drag.
e.preventDefault();
}
}
}, {
key: 'handleTopDragEndCapture',
value: function handleTopDragEndCapture() {
if (this.clearCurrentDragSourceNode()) {
// Firefox can dispatch this event in an infinite loop
// if dragend handler does something like showing an alert.
// Only proceed if we have not handled it already.
this.actions.endDrag();
}
}
}, {
key: 'handleTopDragEnterCapture',
value: function handleTopDragEnterCapture(e) {
this.dragEnterTargetIds = [];
var isFirstEnter = this.enterLeaveCounter.enter(e.target);
if (!isFirstEnter || this.monitor.isDragging()) {
return;
}
var dataTransfer = e.dataTransfer;
var nativeType = (0, _NativeDragSources.matchNativeItemType)(dataTransfer);
if (nativeType) {
// A native item (such as file or URL) dragged from outside the document
this.beginDragNativeItem(nativeType);
}
}
}, {
key: 'handleDragEnter',
value: function handleDragEnter(e, targetId) {
this.dragEnterTargetIds.unshift(targetId);
}
}, {
key: 'handleTopDragEnter',
value: function handleTopDragEnter(e) {
var _this6 = this;
var dragEnterTargetIds = this.dragEnterTargetIds;
this.dragEnterTargetIds = [];
if (!this.monitor.isDragging()) {
// This is probably a native item type we don't understand.
return;
}
this.altKeyPressed = e.altKey;
if (!(0, _BrowserDetector.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
// will still happily dispatch `dragover` despite target being no longer
// there. The easy solution is to only fire `hover` in `dragover` on FF.
this.actions.hover(dragEnterTargetIds, {
clientOffset: (0, _OffsetUtils.getEventClientOffset)(e)
});
}
var canDrop = dragEnterTargetIds.some(function (targetId) {
return _this6.monitor.canDropOnTarget(targetId);
});
if (canDrop) {
// IE requires this to fire dragover events
e.preventDefault();
e.dataTransfer.dropEffect = this.getCurrentDropEffect();
}
}
}, {
key: 'handleTopDragOverCapture',
value: function handleTopDragOverCapture() {
this.dragOverTargetIds = [];
}
}, {
key: 'handleDragOver',
value: function handleDragOver(e, targetId) {
this.dragOverTargetIds.unshift(targetId);
}
}, {
key: 'handleTopDragOver',
value: function handleTopDragOver(e) {
var _this7 = this;
var dragOverTargetIds = this.dragOverTargetIds;
this.dragOverTargetIds = [];
if (!this.monitor.isDragging()) {
// This is probably a native item type we don't understand.
// Prevent default "drop and blow away the whole document" action.
e.preventDefault();
e.dataTransfer.dropEffect = 'none';
return;
}
this.altKeyPressed = e.altKey;
this.actions.hover(dragOverTargetIds, {
clientOffset: (0, _OffsetUtils.getEventClientOffset)(e)
});
var canDrop = dragOverTargetIds.some(function (targetId) {
return _this7.monitor.canDropOnTarget(targetId);
});
if (canDrop) {
// Show user-specified drop effect.
e.preventDefault();
e.dataTransfer.dropEffect = this.getCurrentDropEffect();
} else if (this.isDraggingNativeItem()) {
// Don't show a nice cursor but still prevent default
// "drop and blow away the whole document" action.
e.preventDefault();
e.dataTransfer.dropEffect = 'none';
} else if (this.checkIfCurrentDragSourceRectChanged()) {
// Prevent animating to incorrect position.
// Drop effect must be other than 'none' to prevent animation.
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}
}
}, {
key: 'handleTopDragLeaveCapture',
value: function handleTopDragLeaveCapture(e) {
if (this.isDraggingNativeItem()) {
e.preventDefault();
}
var isLastLeave = this.enterLeaveCounter.leave(e.target);
if (!isLastLeave) {
return;
}
if (this.isDraggingNativeItem()) {
this.endDragNativeItem();
}
}
}, {
key: 'handleTopDropCapture',
value: function handleTopDropCapture(e) {
this.dropTargetIds = [];
e.preventDefault();
if (this.isDraggingNativeItem()) {
this.currentNativeSource.mutateItemByReadingDataTransfer(e.dataTransfer);
}
this.enterLeaveCounter.reset();
}
}, {
key: 'handleDrop',
value: function handleDrop(e, targetId) {
this.dropTargetIds.unshift(targetId);
}
}, {
key: 'handleTopDrop',
value: function handleTopDrop(e) {
var dropTargetIds = this.dropTargetIds;
this.dropTargetIds = [];
this.actions.hover(dropTargetIds, {
clientOffset: (0, _OffsetUtils.getEventClientOffset)(e)
});
this.actions.drop({ dropEffect: this.getCurrentDropEffect() });
if (this.isDraggingNativeItem()) {
this.endDragNativeItem();
} else {
this.endDragIfSourceWasRemovedFromDOM();
}
}
}, {
key: 'handleSelectStart',
value: function handleSelectStart(e) {
var target = e.target;
// Only IE requires us to explicitly say
// we want drag drop operation to start
if (typeof target.dragDrop !== 'function') {
return;
}
// Inputs and textareas should be selectable
if (target.tagName === 'INPUT' || target.tagName === 'SELECT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return;
}
// For other targets, ask IE
// to enable drag and drop
e.preventDefault();
target.dragDrop();
}
}, {
key: 'window',
get: function get() {
if (this.context && this.context.window) {
return this.context.window;
} else if (typeof window !== 'undefined') {
return window;
}
return undefined;
}
}]);
return HTML5Backend;
}();
exports.default = HTML5Backend;