From 2b2959febbf5a9a77a5c88e06e8c5014420cfab4 Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Sun, 17 Nov 2024 10:22:14 -0800 Subject: [PATCH] This is gonna need some cleanup, but it now correctly positions, repositions, reports, and maintains the position of the target *and* the pointer. --- src/draggable/drag-controller.ts | 148 +++++++++++++++++++++++++++++++ src/draggable/events.ts | 36 ++++++++ src/lit-events.ts | 54 +++++++++++ src/screen-size.ts | 24 +++++ src/types.ts | 13 +++ 5 files changed, 275 insertions(+) create mode 100644 src/draggable/drag-controller.ts create mode 100644 src/draggable/events.ts create mode 100644 src/lit-events.ts create mode 100644 src/screen-size.ts create mode 100644 src/types.ts diff --git a/src/draggable/drag-controller.ts b/src/draggable/drag-controller.ts new file mode 100644 index 0000000..883c9d7 --- /dev/null +++ b/src/draggable/drag-controller.ts @@ -0,0 +1,148 @@ +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()); + } +} diff --git a/src/draggable/events.ts b/src/draggable/events.ts new file mode 100644 index 0000000..d7c787a --- /dev/null +++ b/src/draggable/events.ts @@ -0,0 +1,36 @@ +export class LitDragStart extends Event { + static readonly eventName = "lit-drag-start"; + x: number; + y: number; + constructor(x: number, y: number) { + super(LitDragStart.eventName, { bubbles: true, composed: true }); + this.x = x; + this.y = y; + } +} + +export class LitDrag extends Event { + static readonly eventName = "lit-drag"; + x: number; + y: number; + constructor(x: number, y: number) { + super(LitDrag.eventName, { bubbles: true, composed: true }); + this.x = x; + this.y = y; + } +} + +export class LitDragEnd extends Event { + static readonly eventName = "lit-drag-end"; + constructor() { + super(LitDragEnd.eventName, { bubbles: true, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + [LitDragStart.eventName]: LitDragStart; + [LitDrag.eventName]: LitDrag; + [LitDragEnd.eventName]: LitDragEnd; + } +} diff --git a/src/lit-events.ts b/src/lit-events.ts new file mode 100644 index 0000000..36e2bc3 --- /dev/null +++ b/src/lit-events.ts @@ -0,0 +1,54 @@ +import { LitDraggable, LitDragEvent } from "./types"; + +export class LitDragStart extends Event implements LitDragEvent { + static readonly eventName = "lit-drag-start"; + offsetX: number = 0; + offsetY: number = 0; + node: HTMLElement; + // container: HTMLElement; + constructor(source: LitDraggable) { + super(LitDragStart.eventName, { bubbles: true, composed: true }); + this.offsetX = source.translateX; + this.offsetY = source.translateY; + this.node = source.host; + // this.container = source.container; + } +} + +export class LitDragging extends Event implements LitDragEvent { + static readonly eventName = "lit-dragging"; + offsetX: number = 0; + offsetY: number = 0; + node: HTMLElement; + // container: HTMLElement; + constructor(source: LitDraggable) { + super(LitDragging.eventName, { bubbles: true, composed: true }); + this.offsetX = source.translateX; + this.offsetY = source.translateY; + this.node = source.host; + // this.container = source.container; + } +} + +export class LitDragEnd extends Event implements LitDragEvent { + static readonly eventName = "lit-drag-end"; + offsetX: number = 0; + offsetY: number = 0; + node: HTMLElement; + // container: HTMLElement; + constructor(source: LitDraggable) { + super(LitDragEnd.eventName, { bubbles: true, composed: true }); + this.offsetX = source.initialX; + this.offsetY = source.initialY; + this.node = source.host; + // this.container = source.container; + } +} + +declare global { + interface GlobalEventHandlersEventMap { + [LitDragStart.eventName]: LitDragStart; + [LitDragging.eventName]: LitDragging; + [LitDragEnd.eventName]: LitDragEnd; + } +} diff --git a/src/screen-size.ts b/src/screen-size.ts new file mode 100644 index 0000000..b46107d --- /dev/null +++ b/src/screen-size.ts @@ -0,0 +1,24 @@ +import { LitElement, html, css } from "lit"; +import { customElement } from "lit/decorators/custom-element.js"; + +@customElement("screen-size") +export class ScreenSize extends LitElement { + static get styles() { + return css` + div#indicator::before { + font-family: "Cinzel", serif; + font-optical-sizing: auto; + --w: tan(atan2(var(--w_raw), 1px)); + --h: tan(atan2(var(--h_raw), 1px)); + font-size: 3rem; + font-weight: 900; + content: counter(w) "x" counter(h); + counter-reset: h var(--h) w var(--w); + } + `; + } + + render() { + return html`
`; + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..a053696 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,13 @@ +export interface LitDraggable { + translateX: number; + translateY: number; + initialX: number; + initialY: number; + host: HTMLElement; +} + +export interface LitDragEvent extends Event { + offsetX: number; + offsetY: number; + node: HTMLElement; +}