import { LitElement, ReactiveController, ReactiveControllerHost } from "lit"; import { LitDragStart, LitDrag, LitDragEnd } from "./events.js"; export interface MouseTouchLocation { x: number; y: number; } function getTouch(e: TouchEvent, identifier: number): MouseTouchLocation { const touchObj = e.targetTouches ? Array.from(e.targetTouches).find((t) => identifier === t.identifier) : Array.from(e.changedTouches).find((t) => identifier === t.identifier); return { x: touchObj?.clientX ?? -1, y: touchObj?.clientY ?? -1, }; } export function getMouseTouchLocation( ev: MouseEvent | TouchEvent, touchIdentifier: number | undefined ): MouseTouchLocation | undefined { if (!ev.type.startsWith("touch")) { return { x: (ev as MouseEvent).clientX, y: (ev as MouseEvent).clientY, }; } if (touchIdentifier === undefined) { return; } const touchObj = getTouch(ev as TouchEvent, touchIdentifier); return { x: touchObj.x, y: touchObj.y, }; } export const getTouchIdentifier = (e: TouchEvent): number => e.targetTouches && e.targetTouches[0] ? e.targetTouches[0].identifier : e.changedTouches && e.changedTouches[0] ? e.changedTouches[0].identifier : 0; type LitDraggable = ReactiveControllerHost & LitElement; export class DragController implements ReactiveController { host: LitDraggable; touchIdentifier?: number; dragging = false; startX?: number; startY?: number; constructor(host: LitDraggable) { (this.host = host).addController(this); this.dragStart = this.dragStart.bind(this); this.drag = this.drag.bind(this); this.dragEnd = this.dragEnd.bind(this); } hostConnected() { const eventArgs = { capture: true, passive: false }; this.host.addEventListener("mousedown", this.dragStart, eventArgs); this.host.addEventListener("touchstart", this.dragStart, eventArgs); document.addEventListener("mousemove", this.drag, eventArgs); document.addEventListener("touchmove", this.drag, eventArgs); document.addEventListener("mouseup", this.dragEnd, eventArgs); document.addEventListener("touchcancel", this.dragEnd, eventArgs); document.addEventListener("touchend", this.dragEnd, eventArgs); } hostDisconnected() { this.host.removeEventListener("mousedown", this.dragStart); this.host.removeEventListener("touchstart", this.dragStart); document.removeEventListener("mousemove", this.drag); document.removeEventListener("touchmove", this.drag); document.removeEventListener("mouseup", this.dragEnd); document.removeEventListener("touchcancel", this.dragEnd); document.removeEventListener("touchend", this.dragEnd); } private dragStart(ev: MouseEvent | TouchEvent): void { if (ev.type.startsWith("mouse") && (ev as MouseEvent).button !== 0) return; ev.preventDefault(); ev.stopPropagation(); if (ev.type === "touchstart") { this.touchIdentifier = getTouchIdentifier(ev as TouchEvent); } const pos = getMouseTouchLocation(ev, this.touchIdentifier); if (!pos) { return; } this.startX = pos.x; this.startY = pos.y; this.dragging = true; this.host.dispatchEvent(new LitDragStart(this.startX, this.startY)); } private drag(ev: MouseEvent | TouchEvent): void { if (!this.dragging) { return; } ev.preventDefault(); ev.stopPropagation(); const pos = getMouseTouchLocation(ev, this.touchIdentifier); if (!pos) { return; } let deltaX = pos.x - this.startX!; let deltaY = pos.y - this.startY!; if (!deltaX && !deltaY) { return; } this.host.dispatchEvent(new LitDrag(deltaX, deltaY)); } private dragEnd(ev: MouseEvent | TouchEvent): void { if (!this.dragging) { return; } ev.preventDefault(); ev.stopPropagation(); this.touchIdentifier = undefined; this.dragging = false; this.host.dispatchEvent(new LitDragEnd()); } }