fridgemagnets/src/draggable/drag-controller.ts

149 lines
4.3 KiB
TypeScript

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());
}
}