@paperbits/common
Version:
Paperbits common components.
292 lines (232 loc) • 9.85 kB
text/typescript
import * as Utils from "../../utils";
import { DragSource, DragTarget, DragSourceConfig, DragTargetConfig } from "../../ui/draggables";
import { EventManager, Events } from "../../events";
import { ViewManager, ViewManagerMode } from "../viewManager";
import { Box } from "../../editing/box";
const startDraggingTime = 300;
const frictionCoeff = 0.85;
const bounceCoeff = 0.4;
export class DragManager {
private pointerX: number;
private pointerY: number;
private positionX: number;
private positionY: number;
private previousX: number;
private previousY: number;
private velocityX: number;
private velocityY: number;
private startDraggingTimeout: number;
private source: DragSource;
private target: DragTarget;
private acceptBefore: boolean;
private initialOffsetX: number;
private initialOffsetY: number;
private bounds: Box;
public sourceData: any;
public targetData: any;
public dragged: HTMLElement;
public isDragged: boolean;
public draggedClientRect: ClientRect;
constructor(
private readonly eventManager: EventManager,
private readonly viewManager: ViewManager
) {
this.startDragging = this.startDragging.bind(this);
this.completeDragging = this.completeDragging.bind(this);
this.onPointerMove = this.onPointerMove.bind(this);
this.onPointerDown = this.onPointerDown.bind(this);
this.onPointerUp = this.onPointerUp.bind(this);
this.setAcceptor = this.setAcceptor.bind(this);
this.registerDragSource = this.registerDragSource.bind(this);
this.registerDragTarget = this.registerDragTarget.bind(this);
this.resetDraggedElementPosition = this.resetDraggedElementPosition.bind(this);
this.inertia = this.inertia.bind(this);
this.eventManager.addEventListener("onPointerMove", this.onPointerMove);
this.eventManager.addEventListener("onPointerUp", this.onPointerUp);
}
private onPointerMove(event: MouseEvent): void {
if (this.viewManager.mode == ViewManagerMode.preview) {
return;
}
if (!this.isDragged) {
return;
}
if (event.which !== 1) {
this.cancelDragging();
return;
}
this.previousX = this.positionX;
this.previousY = this.positionY;
this.pointerX = event.clientX;
this.pointerY = event.clientY;
this.positionX = this.pointerX - this.initialOffsetX;
this.positionY = this.pointerY - this.initialOffsetY;
if (!this.source.configuration.sticky) {
if (this.positionX > this.bounds.width - this.draggedClientRect.width) {
this.positionX = this.bounds.width - this.draggedClientRect.width;
}
if (this.positionX < 0) {
this.positionX = 0;
}
if (this.positionY > this.bounds.height - this.draggedClientRect.height) {
this.positionY = this.bounds.height - this.draggedClientRect.height;
}
if (this.positionY < 0) {
this.positionY = 0;
}
this.velocityX = this.positionX - this.previousX;
this.velocityY = this.positionY - this.previousY;
}
if (this.target && this.target.element.classList.contains("accepting")) {
this.target.element.classList.remove("accepting");
}
this.resetDraggedElementPosition();
}
private resetDraggedElementPosition(): void {
this.dragged.style.left = this.positionX + "px";
this.dragged.style.top = this.positionY + "px";
}
public startDragging(source: DragSource): void {
this.velocityX = 0;
this.velocityY = 0;
this.isDragged = true;
this.dragged = source.element;
this.sourceData = source.configuration.sourceData;
this.source = source;
this.initialOffsetX = this.source.initialOffsetX;
this.initialOffsetY = this.source.initialOffsetY;
this.pointerX = this.source.initialPointerX;
this.pointerY = this.source.initialPointerY;
this.positionX = this.pointerX - this.initialOffsetX;
this.positionY = this.pointerY - this.initialOffsetY;
this.bounds = {
top: 0,
left: 0,
width: document.body.clientWidth,
height: document.body.clientHeight
};
// Fixating the sizes
if (source.configuration.sticky) {
const rect = this.dragged.getBoundingClientRect();
this.dragged.style.width = rect.width + "px";
this.dragged.style.height = rect.height + "px";
}
if (source.configuration.ondragstart) {
const replacement = source.configuration.ondragstart(source.configuration.sourceData, source.element);
if (replacement) {
this.dragged = replacement;
}
}
if (!this.dragged.parentElement) {
document.body.appendChild(this.dragged);
}
this.dragged.classList.add("dragged");
this.draggedClientRect = source.element.getBoundingClientRect();
this.resetDraggedElementPosition();
this.viewManager.mode = ViewManagerMode.dragging;
}
private cancelDragging(): void {
if (this.viewManager.mode == ViewManagerMode.preview) {
return;
}
this.target = null;
this.completeDragging();
}
private completeDragging(): void {
this.isDragged = false;
if (this.target) {
this.target.element.classList.remove("accepting");
const quadrant = Utils.pointerToClientQuadrant(this.pointerX, this.pointerY, this.target.element);
if (this.target.config.ondrop) {
this.target.config.ondrop(this.sourceData, this.target.config.targetData, quadrant);
}
if (this.target.config.ondropbefore && this.acceptBefore) {
this.target.config.ondropbefore(this.source.configuration.sourceData, this.source.element);
}
if (this.target.config.ondropafter && !this.acceptBefore) {
this.target.config.ondropafter(this.source.configuration.sourceData, this.source.element);
}
}
this.dragged.classList.remove("dragged");
if (this.source.configuration.ondragend) {
this.source.configuration.ondragend(this.source.configuration.sourceData, this.source.element);
this.eventManager.dispatchEvent(Events.VirtualDragEnd);
}
if (this.source.configuration.sticky) {
this.dragged.style.removeProperty("left");
this.dragged.style.removeProperty("top");
this.dragged.style.removeProperty("width");
this.dragged.style.removeProperty("height");
}
else if (this.source.configuration.inertia) {
this.inertia();
}
this.sourceData = null;
this.source = null;
this.target = null;
}
private onPointerUp(event: MouseEvent): void {
if (this.viewManager.mode == ViewManagerMode.preview) {
return;
}
clearTimeout(this.startDraggingTimeout);
if (!this.isDragged) {
return;
}
this.completeDragging();
}
public registerDragSource(element: HTMLElement, config: DragSourceConfig): void {
new DragSource(element, config, this);
}
public registerDragTarget(element: HTMLElement, config: DragTargetConfig): void {
new DragTarget(element, config, this);
}
public onPointerDown(source: DragSource): void {
if (this.viewManager.mode == ViewManagerMode.preview) {
return;
}
if (source.configuration.sticky) {
clearTimeout(this.startDraggingTimeout);
this.startDraggingTimeout = <any>setTimeout(() => this.startDragging(source), startDraggingTime);
}
else {
this.startDragging(source);
}
}
public setAcceptor(acceptor: DragTarget, before: boolean): void {
this.target = acceptor;
if (!this.target.element.classList.contains("accepting")) {
this.target.element.classList.add("accepting");
}
this.acceptBefore = before;
}
public inertia(): void {
if (this.isDragged) {
return;
}
if (Math.abs(this.velocityX) > 0.005 || Math.abs(this.velocityY) > 0.005) {
requestAnimationFrame(this.inertia);
}
this.positionX += this.velocityX;
this.positionY += this.velocityY;
this.velocityX *= frictionCoeff;
this.velocityY *= frictionCoeff;
if (this.positionX > this.bounds.width - this.draggedClientRect.width) {
this.velocityX *= -bounceCoeff;
this.positionX = this.bounds.width - this.draggedClientRect.width;
}
if (this.positionX < 0) {
this.velocityX *= -bounceCoeff;
this.positionX = 0;
}
if (this.positionY > this.bounds.height - this.draggedClientRect.height) {
this.velocityY *= -bounceCoeff;
this.positionY = this.bounds.height - this.draggedClientRect.height;
}
if (this.positionY < 0) {
this.velocityY *= -bounceCoeff;
this.positionY = 0;
}
this.resetDraggedElementPosition();
}
}