UNPKG

azure-devops-ui

Version:

React components for building web UI in Azure DevOps

135 lines (134 loc) 6.72 kB
import "../../CommonImports"; import "../../Core/core.css"; import "./DropdownList.css"; import "./List.css"; import "./ListDropIndicator.css"; import { KeyCode, noop } from '../../Util'; import { dispatchCustomDragEvent, DragDropEffect } from '../../Utilities/DragDrop'; import { cellFromEvent } from "./List"; import { ListDragSourceBehavior } from "./ListDragSourceBehavior"; import { ListDropTargetBehavior } from "./ListDropTargetBehavior"; /** * A behavior that combines the ListDragSourceBehavior and ListDropTargetBehavior. If your list * is only meant to be a drag source, or only meant to be a drop target, then use those two * behaviors individually. * In addition to combining the two behaviors for convenience, this single behavior also enhances * the list with keyboard drag and drop support. */ export class ListDragDropBehavior { constructor(options, dragBehavior, dropBehavior) { this.initialize = (props, dragDroppableUI, eventDispatch) => { this.eventDispatch = eventDispatch; this.itemProvider = props.itemProvider; this.eventDispatch.addEventListener("keydown", this.onKeyDown); this.dragBehavior.initialize(props, dragDroppableUI, eventDispatch); this.dropBehavior.initialize(props, dragDroppableUI, eventDispatch); }; this.onDragRowKeyDown = (event) => { if (event.which === KeyCode.escape) { this.endDrag(event); } else if (event.which === KeyCode.space) { this.endDrag(event, true); } else if (event.which === KeyCode.downArrow) { this.focusIndex = Math.min(this.focusIndex + 1, this.itemProvider.length); this.fireRowDragEvents(event); } else if (event.which === KeyCode.upArrow) { this.focusIndex = Math.max(this.focusIndex - 1, 0); this.fireRowDragEvents(event); } // We don't want the list itself to have a chance to handle these events, while // we are in the middle of a drag operation. event.preventDefault(); }; this.onKeyDown = (event) => { if (!event.defaultPrevented && event.which === KeyCode.space && event.target.tagName !== "INPUT") { // The user has hit spacebar on the row, so we should start the drag/drop operation. const index = cellFromEvent(event).rowIndex; this.focusIndex = index; const item = this.itemProvider.value[index]; this.dataTransfer = { data: item, dropEffect: DragDropEffect.none, secondaryData: { index: index, sourceId: this.options.id }, setDragImage: noop, type: this.options.type }; // Give the consumer a chance to cancel the drag/drop operation dispatchCustomDragEvent("dragstart", event.target, event.nativeEvent, this.dataTransfer); if (this.dataTransfer.effectAllowed !== DragDropEffect.none) { // As long as the operation was not cancelled, save off the // row element that is being dragged and add a keydown listener. // This listener will get called before the event dispatch for the list // itself and allows us to have a way to no change focus as the user // arrows up and down. this.dragItemRowElement = event.target; this.dragItemRowElement.addEventListener("keydown", this.onDragRowKeyDown); } } }; this.options = options; this.dragBehavior = dragBehavior || new ListDragSourceBehavior(options); this.dropBehavior = dropBehavior || new ListDropTargetBehavior(options); } componentDidUpdate(props) { this.itemProvider = props.itemProvider; this.dragBehavior.componentDidUpdate(props); this.dropBehavior.componentDidUpdate(props); } componentWillUnmount() { var _a; (_a = this.eventDispatch) === null || _a === void 0 ? void 0 : _a.removeEventListener("keydown", this.onKeyDown); if (this.dragItemRowElement) { this.dragItemRowElement.removeEventListener("keydown", this.onDragRowKeyDown); } this.dragBehavior.componentWillUnmount(); this.dropBehavior.componentWillUnmount(); } showIndicator(index, position) { this.dropBehavior.showIndicator(index, position); } hideIndicator() { this.dropBehavior.hideIndicator(); } dispatchEventAtIndex(eventType, target, event, index) { // To maintain consistency with mouse-based drag and drop, we want a way to have the row itself fire // the drag events. Since the row element isn't actually associated with the keyboard event that we get, // because the drag row itself is still the source of all of these events, we need a way to find that // row. We do this by first finding the list of our drag row, and then finding the row with the correct // index within that list. let listElement = target; while (listElement) { // We have hit the root of the list, dont look above this. if (listElement.classList.contains("bolt-list")) { break; } listElement = listElement.parentElement; } if (listElement) { const rowTarget = listElement.querySelector("[data-row-index='" + index + "']"); if (rowTarget) { dispatchCustomDragEvent(eventType, rowTarget, event, this.dataTransfer); } } } endDrag(event, drop = false) { dispatchCustomDragEvent("dragend", event.target, event, this.dataTransfer); if (drop) { this.dispatchEventAtIndex("drop", event.target, event, this.focusIndex); } else { this.dispatchEventAtIndex("dragexit", event.target, event, this.focusIndex); } if (this.dragItemRowElement) { this.dragItemRowElement.removeEventListener("keydown", this.onDragRowKeyDown); this.dragItemRowElement = undefined; } } fireRowDragEvents(event) { this.dispatchEventAtIndex("dragenter", event.target, event, this.focusIndex); this.dispatchEventAtIndex("dragover", event.target, event, this.focusIndex); } }