azure-devops-ui
Version:
React components for building web UI in Azure DevOps
164 lines (163 loc) • 8 kB
JavaScript
import "../../CommonImports";
import "../../Core/core.css";
import "./DropdownList.css";
import "./List.css";
import "./ListDropIndicator.css";
import * as React from "react";
import { beginDragOperation, DragDropEffect, DragImage } from '../../Utilities/DragDrop';
import { cellFromEvent } from "./List";
// TODO: FF cleanup target ('dnd-behaviors-parent-autoscroll-enabled' document body class):
const DragAndDropParentAutocrollClassName = 'dnd-behaviors-parent-autoscroll-enabled';
/**
* A behavior that turns a list into the source of a drag and drop operation. Note - this
* behavior should only be used if your list is intended to _only_ be a drag source. Please use
* the ListDragDropBehavior instead if the list is also a drop target, as that is the only way
* to get keyboard drag and drop support within your list.
*/
export class ListDragSourceBehavior {
constructor(options) {
this._scrollableParent = null;
this._isScrollableParentCached = false;
this.initialize = (props, dragDroppableUI, eventDispatch) => {
this.dragDroppableUI = dragDroppableUI;
this.eventDispatch = eventDispatch;
this.eventDispatch.addEventListener("pointerdown", this.onPointerDown);
this.eventDispatch.addEventListener("dragstart", this.onDragStart);
this.eventDispatch.addEventListener("dragend", this.onDragEnd);
this.eventDispatch.addEventListener("dragover", this.onDragging);
this.itemProvider = props.itemProvider;
this._scrollableParent = null;
this._isScrollableParentCached = false;
};
this.onDragging = (event) => {
if (this.options.onDragging) {
this.options.onDragging(event);
return;
}
this.onDraggingDefault();
};
this.onDraggingDefault = () => {
var _a, _b, _c;
// TODO: FF cleanup target ('dnd-behaviors-parent-autoscroll-enabled' document body class):
const isParentAutoscrollEnabled = document.body.classList.contains(DragAndDropParentAutocrollClassName);
const scrollableElement = !isParentAutoscrollEnabled
? (_b = (_a = this.dragDroppableUI) === null || _a === void 0 ? void 0 : _a.currentElement) === null || _b === void 0 ? void 0 : _b.current
: this.getScrollableContainer();
if (!scrollableElement) {
return;
}
;
const { top, bottom } = scrollableElement.getBoundingClientRect();
const speedRate = 20;
const edgeRate = 0.05;
const edgeSize = scrollableElement.offsetHeight * edgeRate;
const viewportY = (_c = this.operation) === null || _c === void 0 ? void 0 : _c.value.y;
if (!viewportY) {
return;
}
const isInBottomEdge = bottom - edgeSize < viewportY;
const isInTopEdge = top + edgeSize > viewportY;
const canScrollUp = scrollableElement.scrollTop > 0;
const canScrollDown = scrollableElement.scrollTop + scrollableElement.offsetHeight < scrollableElement.scrollHeight;
if (isInBottomEdge && canScrollDown) {
scrollableElement.scrollTo({ top: scrollableElement.scrollTop + speedRate });
}
else if (isInTopEdge && canScrollUp) {
scrollableElement.scrollTo({ top: scrollableElement.scrollTop - speedRate });
}
};
this.onDragEnd = (event) => {
const index = cellFromEvent(event).rowIndex;
if (index >= 0 && this.options.onDragEnd) {
this.options.onDragEnd(event);
}
this.dragDroppableUI.removeOverlay("drag-source-item");
this.dragImageData = undefined;
};
this.onDragStart = (event) => {
if (event.detail.dataTransfer) {
const index = cellFromEvent(event).rowIndex;
if (index >= 0) {
if (this.options.onDragStart) {
this.options.onDragStart(event);
}
if (event.detail.dataTransfer.effectAllowed !== DragDropEffect.none) {
this.dragDroppableUI.addOverlay("drag-source-item", index, this.renderDragSourceItemOverlay);
if (this.dragImageData === undefined) {
this.dragImageData = {
image: this.options.renderDragImage(event)
};
}
}
}
}
else {
event.stopPropagation();
event.preventDefault();
}
};
this.onPointerDown = (event) => {
if (event.button === 0) {
this.beginDrag(event);
}
};
this.renderDragSourceItemOverlay = (props) => {
return (React.createElement(React.Fragment, null,
React.createElement("div", { className: "bolt-list-drag-source-item flex-grow" }),
this.operation && this.dragImageData && React.createElement(DragImage, { operation: this.operation }, this.dragImageData.image)));
};
this.setDragImage = (image, xOffset, yOffset) => {
this.dragImageData = { image: image, xOffset: xOffset, yOffset: yOffset };
};
this.options = options;
}
componentDidUpdate(props) {
this.itemProvider = props.itemProvider;
this._scrollableParent = null;
this._isScrollableParentCached = false;
}
componentWillUnmount() {
var _a, _b, _c, _d;
(_a = this.eventDispatch) === null || _a === void 0 ? void 0 : _a.removeEventListener("pointerdown", this.onPointerDown);
(_b = this.eventDispatch) === null || _b === void 0 ? void 0 : _b.removeEventListener("dragstart", this.onDragStart);
(_c = this.eventDispatch) === null || _c === void 0 ? void 0 : _c.removeEventListener("dragend", this.onDragEnd);
(_d = this.eventDispatch) === null || _d === void 0 ? void 0 : _d.removeEventListener("dragover", this.onDragging);
}
beginDrag(event) {
const index = cellFromEvent(event).rowIndex;
if (this.itemProvider && index >= 0) {
const item = this.itemProvider.value[index];
this.operation = beginDragOperation(event, {
data: item,
dropEffect: DragDropEffect.none,
secondaryData: { index: index, sourceId: this.options.id },
setDragImage: this.setDragImage,
type: this.options.type
});
}
}
getScrollableContainer() {
var _a, _b, _c;
if (this._isScrollableParentCached) {
return this._scrollableParent;
}
this._isScrollableParentCached = true;
const dragDroppableElement = (_b = (_a = this.dragDroppableUI) === null || _a === void 0 ? void 0 : _a.currentElement) === null || _b === void 0 ? void 0 : _b.current;
if (!dragDroppableElement) {
return null;
}
this._scrollableParent = this.findScrollableParent(dragDroppableElement);
return (_c = this._scrollableParent) !== null && _c !== void 0 ? _c : dragDroppableElement;
}
findScrollableParent(element) {
while (element) {
const style = window.getComputedStyle(element);
const overflowY = style.overflowY;
if ((overflowY === 'auto' || overflowY === 'scroll') && element.scrollHeight > element.clientHeight) {
return element;
}
element = element.parentElement;
}
return null;
}
}