This is gonna need some cleanup, but it now correctly positions, repositions, reports, and maintains the position of the target *and* the pointer.

This commit is contained in:
Elf M. Sternberg 2024-11-17 10:22:14 -08:00
parent 93fb8f1c2e
commit 2b2959febb
5 changed files with 275 additions and 0 deletions

View File

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

36
src/draggable/events.ts Normal file
View File

@ -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;
}
}

54
src/lit-events.ts Normal file
View File

@ -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;
}
}

24
src/screen-size.ts Normal file
View File

@ -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`<div id="indicator"></div>`;
}
}

13
src/types.ts Normal file
View File

@ -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;
}