318 lines
14 KiB
JavaScript
318 lines
14 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.DragDropHelper = void 0;
|
|
var Utilities_1 = require("../../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;
|
|
// To make this class cheap to create, which allows simplifying some logic elsewhere,
|
|
// only initialize the event group and global event handlers as needed.
|
|
this._initialized = false;
|
|
}
|
|
DragDropHelper.prototype.dispose = function () {
|
|
if (this._events) {
|
|
this._events.dispose();
|
|
}
|
|
};
|
|
DragDropHelper.prototype.subscribe = function (root, events, dragDropOptions) {
|
|
var _this = this;
|
|
if (!this._initialized) {
|
|
this._events = new Utilities_1.EventGroup(this);
|
|
var doc = (0, Utilities_1.getDocument)();
|
|
// clear drag data when mouse up, use capture event to ensure it will be run
|
|
if (doc) {
|
|
this._events.on(doc.body, 'mouseup', this._onMouseUp.bind(this), true);
|
|
this._events.on(doc, 'mouseup', this._onDocumentMouseUp.bind(this), true);
|
|
}
|
|
this._initialized = true;
|
|
}
|
|
var _a = dragDropOptions.key, key = _a === void 0 ? "".concat(++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 = dragDropOptions;
|
|
if (options && options.onDragStart) {
|
|
options.onDragStart(options.context.data, options.context.index, _this._selection.getSelection(), event);
|
|
}
|
|
_this._isDragging = true;
|
|
if (event.dataTransfer) {
|
|
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
|
|
Utilities_1.EventGroup.raise(this._dragData.dropTarget.root, 'dragleave');
|
|
Utilities_1.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) {
|
|
var doc = (0, Utilities_1.getDocument)();
|
|
if (doc && event.target === doc.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) {
|
|
Utilities_1.EventGroup.raise(this._dragData.dropTarget.root, 'dragleave');
|
|
Utilities_1.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) {
|
|
Utilities_1.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 (parentElement, childElement) {
|
|
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;
|
|
}());
|
|
exports.DragDropHelper = DragDropHelper;
|
|
//# sourceMappingURL=DragDropHelper.js.map
|