first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
@@ -0,0 +1,53 @@
import { EventGroup } from '../../Utilities';
import type { IDragDropHelper, IDragDropOptions } from './interfaces';
import type { ISelection } from '../../utilities/selection/interfaces';
export interface IDragDropHelperParams {
selection: ISelection;
minimumPixelsForDrag?: number;
}
export declare class DragDropHelper implements IDragDropHelper {
private _dragEnterCounts;
private _isDragging;
private _dragData;
private _selection;
private _activeTargets;
private _events;
private _lastId;
private _initialized;
constructor(params: IDragDropHelperParams);
dispose(): void;
subscribe(root: HTMLElement, events: EventGroup, dragDropOptions: IDragDropOptions): {
key: string;
dispose(): void;
};
unsubscribe(root: HTMLElement, key: string): void;
private _onDragEnd;
/**
* clear drag data when mouse up on body
*/
private _onMouseUp;
/**
* clear drag data when mouse up outside of the document
*/
private _onDocumentMouseUp;
/**
* 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.
*/
private _onMouseMove;
/**
* when mouse leave a target while dragging some items, fire dragleave to the target
*/
private _onMouseLeave;
/**
* when mouse down on a draggable item, we start to track dragdata.
*/
private _onMouseDown;
/**
* determine whether the child target is a descendant of the parent
*/
private _isChild;
private _isDraggable;
private _isDroppable;
}
+315
View File
@@ -0,0 +1,315 @@
import { EventGroup, getDocument } 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;
// 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 EventGroup(this);
var doc = 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
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) {
var doc = 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) {
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 (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;
}());
export { DragDropHelper };
//# sourceMappingURL=DragDropHelper.js.map
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
export * from './interfaces';
export * from './DragDropHelper';
+3
View File
@@ -0,0 +1,3 @@
export * from './interfaces';
export * from './DragDropHelper';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"../src/","sources":["utilities/dragdrop/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC","sourcesContent":["export * from './interfaces';\nexport * from './DragDropHelper';\n"]}
+155
View File
@@ -0,0 +1,155 @@
import { EventGroup } from '../../Utilities';
/**
* Helper for subscribing and unsubscribing to
* drag and drop events on an HTMLElement.
*
* {@docCategory IDragDropHelper}
*/
export interface IDragDropHelper {
/**
* Subscribe to events on a DOM node with drag and drop configuration.
*/
subscribe: (root: HTMLElement, events: EventGroup, options: IDragDropOptions) => {
key: string;
dispose: () => void;
};
/**
* Unsubscribe to events registered on a DOM node with key.
*/
unsubscribe: (root: HTMLElement, key: string) => void;
/**
* Dispose of listeners bound to instance of helper.
*/
dispose: () => void;
}
/**
* Drag & drop event callback interface.
*
* {@docCategory IDragDropHelper}
*/
export interface IDragDropEvents {
/**
* Whether or not drop action is allowed.
*/
canDrop?: (dropContext?: IDragDropContext, dragContext?: IDragDropContext) => boolean;
/**
* Whether or not drag action is allowed.
*/
canDrag?: (item?: any) => boolean;
/**
* On drag enter region event callback.
* Returned string is the css classes that will be added to the entering element.
*/
onDragEnter?: (item?: any, event?: DragEvent) => string;
/**
* On drag leave region event callback.
*/
onDragLeave?: (item?: any, event?: DragEvent) => void;
/**
* On drop event callback.
*/
onDrop?: (item?: any, event?: DragEvent) => void;
/**
* On drag start event callback.
*/
onDragStart?: (item?: any, itemIndex?: number, selectedItems?: any[], event?: MouseEvent) => void;
/**
* On drag end event callback.
*/
onDragEnd?: (item?: any, event?: DragEvent) => void;
/**
* On drag over event callback.
*/
onDragOver?: (item?: any, event?: DragEvent) => void;
/**
* Whether the whole group is draggable. This applies after canDrag returns true for the group.
*/
canDragGroups?: boolean;
}
/**
* Drag & drop event contextual information.
*
* {@docCategory IDragDropHelper}
*/
export interface IDragDropContext {
/**
* Data associated with drag & drop action.
*/
data: any;
/**
* Index of drag & drop action.
*/
index: number;
/**
* Whether or not drag & drop region is indivual or group of content.
*/
isGroup?: boolean;
}
export interface IDragDropTarget {
root: HTMLElement;
options: IDragDropOptions;
key: string;
}
/**
* The drag and drop event listener configuration.
*
* {@docCategory IDragDropHelper}
*/
export interface IDragDropOptions {
/**
* Unique key to associate with instance.
*/
key?: string;
/**
* Map of event name to callback function to subscribe to.
*/
eventMap?: {
eventName: string;
callback: (context: IDragDropContext, event?: any) => void;
}[];
/**
* Selection index on drag and drop event.
*/
selectionIndex: number;
/**
* Context associated with drag and drop event.
*/
context: IDragDropContext;
/**
* Callback on drop state update.
*/
updateDropState: (isDropping: boolean, event: DragEvent) => void;
/**
* Whether or not drop action is allowed.
*/
canDrop?: (dropContext?: IDragDropContext, dragContext?: IDragDropContext) => boolean;
/**
* Whether or not drag action is allowed.
*/
canDrag?: (item?: any) => boolean;
/**
* On drag start event callback.
*/
onDragStart?: (item?: any, itemIndex?: number, selectedItems?: any[], event?: MouseEvent) => void;
/**
* On drop event callback.
*/
onDrop?: (item?: any, event?: DragEvent) => void;
/**
* On drag end event callback.
*/
onDragEnd?: (item?: any, event?: DragEvent) => void;
/**
* On drag over element(s) event callback.
*/
onDragOver?: (item?: any, event?: DragEvent) => void;
}
/**
* {@docCategory IDragDropHelper}
*/
export interface IDragDropEvent {
/**
* Whether or not the drag & drop event was handled.
*/
isHandled?: boolean;
}
+2
View File
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=interfaces.js.map
@@ -0,0 +1 @@
{"version":3,"file":"interfaces.js","sourceRoot":"../src/","sources":["utilities/dragdrop/interfaces.ts"],"names":[],"mappings":"","sourcesContent":["import { EventGroup } from '../../Utilities';\n\n/**\n * Helper for subscribing and unsubscribing to\n * drag and drop events on an HTMLElement.\n *\n * {@docCategory IDragDropHelper}\n */\nexport interface IDragDropHelper {\n /**\n * Subscribe to events on a DOM node with drag and drop configuration.\n */\n subscribe: (\n root: HTMLElement,\n events: EventGroup,\n options: IDragDropOptions,\n ) => {\n key: string;\n dispose: () => void;\n };\n /**\n * Unsubscribe to events registered on a DOM node with key.\n */\n unsubscribe: (root: HTMLElement, key: string) => void;\n /**\n * Dispose of listeners bound to instance of helper.\n */\n dispose: () => void;\n}\n\n/**\n * Drag & drop event callback interface.\n *\n * {@docCategory IDragDropHelper}\n */\nexport interface IDragDropEvents {\n /**\n * Whether or not drop action is allowed.\n */\n canDrop?: (dropContext?: IDragDropContext, dragContext?: IDragDropContext) => boolean;\n /**\n * Whether or not drag action is allowed.\n */\n canDrag?: (item?: any) => boolean;\n /**\n * On drag enter region event callback.\n * Returned string is the css classes that will be added to the entering element.\n */\n onDragEnter?: (item?: any, event?: DragEvent) => string;\n /**\n * On drag leave region event callback.\n */\n onDragLeave?: (item?: any, event?: DragEvent) => void;\n /**\n * On drop event callback.\n */\n onDrop?: (item?: any, event?: DragEvent) => void;\n /**\n * On drag start event callback.\n */\n onDragStart?: (item?: any, itemIndex?: number, selectedItems?: any[], event?: MouseEvent) => void;\n /**\n * On drag end event callback.\n */\n onDragEnd?: (item?: any, event?: DragEvent) => void;\n /**\n * On drag over event callback.\n */\n onDragOver?: (item?: any, event?: DragEvent) => void;\n /**\n * Whether the whole group is draggable. This applies after canDrag returns true for the group.\n */\n canDragGroups?: boolean;\n}\n\n/**\n * Drag & drop event contextual information.\n *\n * {@docCategory IDragDropHelper}\n */\nexport interface IDragDropContext {\n /**\n * Data associated with drag & drop action.\n */\n data: any;\n /**\n * Index of drag & drop action.\n */\n index: number;\n /**\n * Whether or not drag & drop region is indivual or group of content.\n */\n isGroup?: boolean;\n}\n\nexport interface IDragDropTarget {\n root: HTMLElement;\n options: IDragDropOptions;\n key: string;\n}\n\n/**\n * The drag and drop event listener configuration.\n *\n * {@docCategory IDragDropHelper}\n */\nexport interface IDragDropOptions {\n /**\n * Unique key to associate with instance.\n */\n key?: string;\n /**\n * Map of event name to callback function to subscribe to.\n */\n eventMap?: {\n eventName: string;\n callback: (context: IDragDropContext, event?: any) => void;\n }[];\n /**\n * Selection index on drag and drop event.\n */\n selectionIndex: number;\n /**\n * Context associated with drag and drop event.\n */\n context: IDragDropContext;\n /**\n * Callback on drop state update.\n */\n updateDropState: (isDropping: boolean, event: DragEvent) => void;\n /**\n * Whether or not drop action is allowed.\n */\n canDrop?: (dropContext?: IDragDropContext, dragContext?: IDragDropContext) => boolean;\n /**\n * Whether or not drag action is allowed.\n */\n canDrag?: (item?: any) => boolean;\n /**\n * On drag start event callback.\n */\n onDragStart?: (item?: any, itemIndex?: number, selectedItems?: any[], event?: MouseEvent) => void;\n /**\n * On drop event callback.\n */\n onDrop?: (item?: any, event?: DragEvent) => void;\n /**\n * On drag end event callback.\n */\n onDragEnd?: (item?: any, event?: DragEvent) => void;\n /**\n * On drag over element(s) event callback.\n */\n onDragOver?: (item?: any, event?: DragEvent) => void;\n}\n\n/**\n * {@docCategory IDragDropHelper}\n */\nexport interface IDragDropEvent {\n /**\n * Whether or not the drag & drop event was handled.\n */\n isHandled?: boolean;\n}\n"]}