diff options
author | Tyler Schicke <tyler_schicke@brown.edu> | 2019-01-26 17:23:25 -0500 |
---|---|---|
committer | Tyler Schicke <tyler_schicke@brown.edu> | 2019-01-26 17:23:25 -0500 |
commit | ba3c6773a04ea83facab1f67db0025d6185c2c65 (patch) | |
tree | be39c0c927ae4649c8505ef33c7f1c8272974ca2 /src/util/DragManager.ts | |
parent | f98e634ae7070cd841bc523514d147195308696c (diff) | |
parent | 122076af3edfd432e6abe3b2571f21034d5c16e5 (diff) |
Merge branch 'master' into move_doc_get_out_the_way
Diffstat (limited to 'src/util/DragManager.ts')
-rw-r--r-- | src/util/DragManager.ts | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/src/util/DragManager.ts b/src/util/DragManager.ts new file mode 100644 index 000000000..3111d589f --- /dev/null +++ b/src/util/DragManager.ts @@ -0,0 +1,155 @@ +import { Opt } from "../fields/Field"; + +export namespace DragManager { + export let rootId = "root"; + let dragDiv: HTMLDivElement; + + export enum DragButtons { + Left = 1, Right = 2, Both = Left | Right + } + + interface DragOptions { + handlers: DragHandlers; + + buttons: number; + } + + export interface DragDropDisposer { + (): void; + } + + export class DragStartEvent { + private _cancelled: boolean = false; + get cancelled() { return this._cancelled }; + + cancel() { this._cancelled = true; }; + } + + export class DragCompleteEvent { + + } + + export interface DragHandlers { + dragStart: (e: DragStartEvent) => void; + dragComplete: (e: DragCompleteEvent) => void; + } + + export interface DropOptions { + handlers: DropHandlers; + } + + export class DropEvent { + constructor(readonly x: number, readonly y: number) { } + } + + export interface DropHandlers { + drop: (e: DropEvent) => void; + } + + export function MakeDraggable(element: HTMLElement, options: DragOptions): DragDropDisposer { + if ("draggable" in element.dataset) { + throw new Error("Element is already draggable, can't make it draggable again"); + } + element.dataset["draggable"] = "true"; + const dispose = () => { + document.removeEventListener("pointerup", upHandler); + document.removeEventListener("pointermove", startDragHandler); + } + const startDragHandler = (e: PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + dispose(); + StartDrag(element, e, options); + } + const upHandler = (e: PointerEvent) => { + dispose(); + }; + const downHandler = (e: PointerEvent) => { + e.stopPropagation(); + document.addEventListener("pointermove", startDragHandler); + document.addEventListener("pointerup", upHandler); + }; + element.addEventListener("pointerdown", downHandler); + + return () => { + element.removeEventListener("pointerdown", downHandler); + delete element.dataset["draggable"]; + } + } + + export function MakeDropTarget(element: HTMLElement, options: DropOptions): DragDropDisposer { + if ("draggable" in element.dataset) { + throw new Error("Element is already droppable, can't make it droppable again"); + } + element.dataset["canDrop"] = "true"; + const handler = (e: Event) => { + const ce = e as CustomEvent<DropEvent>; + options.handlers.drop(ce.detail); + }; + element.addEventListener("dashOnDrop", handler); + return () => { + element.removeEventListener("dashOnDrop", handler); + delete element.dataset["canDrop"] + }; + } + + function StartDrag(ele: HTMLElement, e: PointerEvent, options: DragOptions) { + if (!dragDiv) { + const root = document.getElementById(rootId); + if (!root) { + throw new Error("No root element found"); + } + dragDiv = document.createElement("div"); + root.appendChild(dragDiv); + } + if ((e.buttons & options.buttons) === 0) { + return; + } + let event = new DragStartEvent(); + options.handlers.dragStart(event); + if (event.cancelled) { + return; + } + const w = ele.offsetWidth, h = ele.offsetHeight; + const rect = ele.getBoundingClientRect(); + const scaleX = rect.width / w, scaleY = rect.height / h; + let x = rect.left, y = rect.top; + // const offsetX = e.x - rect.left, offsetY = e.y - rect.top; + let dragElement = ele.cloneNode(true) as HTMLElement; + dragElement.style.opacity = "0.7"; + dragElement.style.position = "absolute"; + dragElement.style.transformOrigin = "0 0"; + dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; + dragDiv.appendChild(dragElement); + + const moveHandler = (e: PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + x += e.movementX; + y += e.movementY; + dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; + }; + const upHandler = (e: PointerEvent) => { + document.removeEventListener("pointermove", moveHandler, true); + document.removeEventListener("pointerup", upHandler); + FinishDrag(dragElement, e, options); + }; + document.addEventListener("pointermove", moveHandler, true); + document.addEventListener("pointerup", upHandler); + } + + function FinishDrag(ele: HTMLElement, e: PointerEvent, options: DragOptions) { + dragDiv.removeChild(ele); + const target = document.elementFromPoint(e.x, e.y); + if (!target) { + return; + } + target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", { + bubbles: true, + detail: { + x: e.x, + y: e.y, + } + })); + } +}
\ No newline at end of file |