office-ui-fabric-react
Version:
Reusable React components for building experiences for Office 365.
304 lines • 14.5 kB
JavaScript
import * as ReactDOM from 'react-dom';
import { EventGroup } from '../../Utilities';
var MOUSEDOWN_PRIMARY_BUTTON = 0; // for mouse down event we are using ev.button property, 0 means left button
var MOUSEMOVE_PRIMARY_BUTTON = 1; // for mouse move event we are using ev.buttons property, 1 means left button
var DragDropHelper = /** @class */ (function () {
function DragDropHelper(params) {
this._selection = params.selection;
this._dragEnterCounts = {};
this._activeTargets = {};
this._lastId = 0;
this._events = new EventGroup(this);
// clear drag data when mouse up, use capture event to ensure it will be run
this._events.on(document.body, 'mouseup', this._onMouseUp.bind(this), true);
this._events.on(document, 'mouseup', this._onDocumentMouseUp.bind(this), true);
}
DragDropHelper.prototype.dispose = function () {
this._events.dispose();
};
DragDropHelper.prototype.subscribe = function (root, events, dragDropOptions) {
var _this = this;
var _a = dragDropOptions.key, key = _a === void 0 ? "" + ++this._lastId : _a;
var handlers = [];
var onDragStart;
var onDragLeave;
var onDragEnter;
var onDragEnd;
var onDrop;
var onDragOver;
var onMouseDown;
var isDraggable;
var isDroppable;
var activeTarget;
if (dragDropOptions && root) {
var eventMap = dragDropOptions.eventMap, context = dragDropOptions.context, updateDropState_1 = dragDropOptions.updateDropState;
var dragDropTarget = {
root: root,
options: dragDropOptions,
key: key
};
isDraggable = this._isDraggable(dragDropTarget);
isDroppable = this._isDroppable(dragDropTarget);
if (isDraggable || isDroppable) {
if (eventMap) {
for (var _i = 0, eventMap_1 = eventMap; _i < eventMap_1.length; _i++) {
var event_1 = eventMap_1[_i];
var handler = {
callback: event_1.callback.bind(null, context),
eventName: event_1.eventName
};
handlers.push(handler);
this._events.on(root, handler.eventName, handler.callback);
}
}
}
if (isDroppable) {
// If the target is droppable, wire up global event listeners to track drop-related events.
onDragLeave = function (event) {
if (!event.isHandled) {
event.isHandled = true;
_this._dragEnterCounts[key]--;
if (_this._dragEnterCounts[key] === 0) {
updateDropState_1(false /* isDropping */, event);
}
}
};
onDragEnter = function (event) {
event.preventDefault(); // needed for IE
if (!event.isHandled) {
event.isHandled = true;
_this._dragEnterCounts[key]++;
if (_this._dragEnterCounts[key] === 1) {
updateDropState_1(true /* isDropping */, event);
}
}
};
onDragEnd = function (event) {
_this._dragEnterCounts[key] = 0;
updateDropState_1(false /* isDropping */, event);
};
onDrop = function (event) {
_this._dragEnterCounts[key] = 0;
updateDropState_1(false /* isDropping */, event);
if (dragDropOptions.onDrop) {
dragDropOptions.onDrop(dragDropOptions.context.data, event);
}
};
onDragOver = function (event) {
event.preventDefault();
if (dragDropOptions.onDragOver) {
dragDropOptions.onDragOver(dragDropOptions.context.data, event);
}
};
this._dragEnterCounts[key] = 0;
// dragenter and dragleave will be fired when hover to the child element
// but we only want to change state when enter or leave the current element
// use the count to ensure it.
events.on(root, 'dragenter', onDragEnter);
events.on(root, 'dragleave', onDragLeave);
events.on(root, 'dragend', onDragEnd);
events.on(root, 'drop', onDrop);
events.on(root, 'dragover', onDragOver);
}
if (isDraggable) {
// If the target is draggable, wire up local event listeners for mouse events.
onMouseDown = this._onMouseDown.bind(this, dragDropTarget);
onDragEnd = this._onDragEnd.bind(this, dragDropTarget);
// We need to add in data so that on Firefox we show the ghost element when dragging
onDragStart = function (event) {
var options = _this._dragData.dragTarget.options;
if (options && options.onDragStart) {
options.onDragStart(options.context.data, options.context.index, _this._selection.getSelection(), event);
}
_this._isDragging = true;
event.dataTransfer.setData('id', root.id);
};
events.on(root, 'dragstart', onDragStart);
events.on(root, 'mousedown', onMouseDown);
events.on(root, 'dragend', onDragEnd);
}
activeTarget = {
target: dragDropTarget,
dispose: function () {
if (_this._activeTargets[key] === activeTarget) {
delete _this._activeTargets[key];
}
if (root) {
for (var _i = 0, handlers_1 = handlers; _i < handlers_1.length; _i++) {
var handler = handlers_1[_i];
_this._events.off(root, handler.eventName, handler.callback);
}
if (isDroppable) {
events.off(root, 'dragenter', onDragEnter);
events.off(root, 'dragleave', onDragLeave);
events.off(root, 'dragend', onDragEnd);
events.off(root, 'dragover', onDragOver);
events.off(root, 'drop', onDrop);
}
if (isDraggable) {
events.off(root, 'dragstart', onDragStart);
events.off(root, 'mousedown', onMouseDown);
events.off(root, 'dragend', onDragEnd);
}
}
}
};
this._activeTargets[key] = activeTarget;
}
return {
key: key,
dispose: function () {
if (activeTarget) {
activeTarget.dispose();
}
}
};
};
DragDropHelper.prototype.unsubscribe = function (root, key) {
var activeTarget = this._activeTargets[key];
if (activeTarget) {
activeTarget.dispose();
}
};
DragDropHelper.prototype._onDragEnd = function (target, event) {
var options = target.options;
if (options.onDragEnd) {
options.onDragEnd(options.context.data, event);
}
};
/**
* clear drag data when mouse up on body
*/
DragDropHelper.prototype._onMouseUp = function (event) {
this._isDragging = false;
if (this._dragData) {
for (var _i = 0, _a = Object.keys(this._activeTargets); _i < _a.length; _i++) {
var key = _a[_i];
var activeTarget = this._activeTargets[key];
if (activeTarget.target.root) {
this._events.off(activeTarget.target.root, 'mousemove');
this._events.off(activeTarget.target.root, 'mouseleave');
}
}
if (this._dragData.dropTarget) {
// raise dragleave event to let dropTarget know it need to remove dropping style
EventGroup.raise(this._dragData.dropTarget.root, 'dragleave');
EventGroup.raise(this._dragData.dropTarget.root, 'drop');
}
}
this._dragData = null;
};
/**
* clear drag data when mouse up outside of the document
*/
DragDropHelper.prototype._onDocumentMouseUp = function (event) {
if (event.target === document.documentElement) {
this._onMouseUp(event);
}
};
/**
* when mouse move over a new drop target while dragging some items,
* fire dragleave on the old target and fire dragenter to the new target
* The target will handle style change on dragenter and dragleave events.
*/
DragDropHelper.prototype._onMouseMove = function (target, event) {
var
// use buttons property here since ev.button in some edge case is not updating well during the move.
// but firefox doesn't support it, so we set the default value when it is not defined.
_a = event.buttons,
// use buttons property here since ev.button in some edge case is not updating well during the move.
// but firefox doesn't support it, so we set the default value when it is not defined.
buttons = _a === void 0 ? MOUSEMOVE_PRIMARY_BUTTON : _a;
if (this._dragData && buttons !== MOUSEMOVE_PRIMARY_BUTTON) {
// cancel mouse down event and return early when the primary button is not pressed
this._onMouseUp(event);
return;
}
var root = target.root, key = target.key;
if (this._isDragging) {
if (this._isDroppable(target)) {
// we can have nested drop targets in the DOM, like a folder inside a group. In that case, when we drag into
// the inner target (folder), we first set dropTarget to the inner element. But the same event is bubbled to the
// outer target too, and we need to prevent the outer one from taking over.
// So, check if the last dropTarget is not a child of the current.
if (this._dragData) {
if (this._dragData.dropTarget &&
this._dragData.dropTarget.key !== key &&
!this._isChild(root, this._dragData.dropTarget.root)) {
if (this._dragEnterCounts[this._dragData.dropTarget.key] > 0) {
EventGroup.raise(this._dragData.dropTarget.root, 'dragleave');
EventGroup.raise(root, 'dragenter');
this._dragData.dropTarget = target;
}
}
}
}
}
};
/**
* when mouse leave a target while dragging some items, fire dragleave to the target
*/
DragDropHelper.prototype._onMouseLeave = function (target, event) {
if (this._isDragging) {
if (this._dragData && this._dragData.dropTarget && this._dragData.dropTarget.key === target.key) {
EventGroup.raise(target.root, 'dragleave');
this._dragData.dropTarget = undefined;
}
}
};
/**
* when mouse down on a draggable item, we start to track dragdata.
*/
DragDropHelper.prototype._onMouseDown = function (target, event) {
if (event.button !== MOUSEDOWN_PRIMARY_BUTTON) {
// Ignore anything except the primary button.
return;
}
if (this._isDraggable(target)) {
this._dragData = {
clientX: event.clientX,
clientY: event.clientY,
eventTarget: event.target,
dragTarget: target
};
for (var _i = 0, _a = Object.keys(this._activeTargets); _i < _a.length; _i++) {
var key = _a[_i];
var activeTarget = this._activeTargets[key];
if (activeTarget.target.root) {
this._events.on(activeTarget.target.root, 'mousemove', this._onMouseMove.bind(this, activeTarget.target));
this._events.on(activeTarget.target.root, 'mouseleave', this._onMouseLeave.bind(this, activeTarget.target));
}
}
}
else {
this._dragData = null;
}
};
/**
* determine whether the child target is a descendant of the parent
*/
DragDropHelper.prototype._isChild = function (parent, child) {
var parentElement = ReactDOM.findDOMNode(parent);
var childElement = ReactDOM.findDOMNode(child);
while (childElement && childElement.parentElement) {
if (childElement.parentElement === parentElement) {
return true;
}
childElement = childElement.parentElement;
}
return false;
};
DragDropHelper.prototype._isDraggable = function (target) {
var options = target.options;
return !!(options.canDrag && options.canDrag(options.context.data));
};
DragDropHelper.prototype._isDroppable = function (target) {
// TODO: take the drag item into consideration to prevent dragging an item into the same group
var options = target.options;
var dragContext = this._dragData && this._dragData.dragTarget ? this._dragData.dragTarget.options.context : undefined;
return !!(options.canDrop && options.canDrop(options.context, dragContext));
};
return DragDropHelper;
}());
export { DragDropHelper };
//# sourceMappingURL=DragDropHelper.js.map