UNPKG

@paperbits/common

Version:
292 lines (232 loc) 9.85 kB
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(); } }