mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 14:36:31 +00:00
125 lines
4.3 KiB
TypeScript
125 lines
4.3 KiB
TypeScript
interface PlannerWaypointCoordinate {
|
|
lat: number;
|
|
lng: number;
|
|
}
|
|
|
|
interface PlannerWaypointDragOptions {
|
|
element: HTMLElement;
|
|
initial: PlannerWaypointCoordinate;
|
|
coordinateAt: (clientX: number, clientY: number) => PlannerWaypointCoordinate;
|
|
setPosition: (coordinate: PlannerWaypointCoordinate) => void;
|
|
onCommit: (coordinate: PlannerWaypointCoordinate) => boolean | void;
|
|
}
|
|
|
|
const DRAG_THRESHOLD_PX = 4;
|
|
|
|
export function wirePlannerWaypointDrag({
|
|
element,
|
|
initial,
|
|
coordinateAt,
|
|
setPosition,
|
|
onCommit,
|
|
}: PlannerWaypointDragOptions): () => void {
|
|
let pointerId: number | null = null;
|
|
let start: { x: number; y: number } | null = null;
|
|
let dragged = false;
|
|
let suppressClick = false;
|
|
const originalTouchAction = element.style.touchAction;
|
|
const originalUserSelect = element.style.userSelect;
|
|
|
|
const stopMapGesture = (event: Event) => event.stopPropagation();
|
|
const restore = () => setPosition(initial);
|
|
const cancel = (event?: Event) => {
|
|
if (pointerId === null) return;
|
|
event?.stopPropagation();
|
|
restore();
|
|
pointerId = null;
|
|
start = null;
|
|
dragged = false;
|
|
suppressClick = false;
|
|
};
|
|
|
|
const onPointerDown = (event: PointerEvent) => {
|
|
if (event.button !== 0 || pointerId !== null) {
|
|
event.stopPropagation();
|
|
return;
|
|
}
|
|
event.stopPropagation();
|
|
pointerId = event.pointerId;
|
|
start = { x: event.clientX, y: event.clientY };
|
|
dragged = false;
|
|
suppressClick = false;
|
|
try {
|
|
element.setPointerCapture(event.pointerId);
|
|
} catch {
|
|
// Pointer capture is unavailable in some test DOMs and older browsers.
|
|
}
|
|
};
|
|
|
|
const onPointerMove = (event: PointerEvent) => {
|
|
if (event.pointerId !== pointerId || !start) return;
|
|
event.stopPropagation();
|
|
if (!dragged && Math.hypot(event.clientX - start.x, event.clientY - start.y) < DRAG_THRESHOLD_PX) return;
|
|
dragged = true;
|
|
event.preventDefault();
|
|
setPosition(coordinateAt(event.clientX, event.clientY));
|
|
};
|
|
|
|
const onPointerUp = (event: PointerEvent) => {
|
|
if (event.pointerId !== pointerId || !start) return;
|
|
event.stopPropagation();
|
|
const travelled = Math.hypot(event.clientX - start.x, event.clientY - start.y);
|
|
if (dragged || travelled >= DRAG_THRESHOLD_PX) {
|
|
const coordinate = coordinateAt(event.clientX, event.clientY);
|
|
setPosition(coordinate);
|
|
const accepted = onCommit(coordinate) !== false;
|
|
suppressClick = true;
|
|
if (!accepted) restore();
|
|
}
|
|
pointerId = null;
|
|
start = null;
|
|
dragged = false;
|
|
};
|
|
|
|
const onClickCapture = (event: MouseEvent) => {
|
|
if (!suppressClick) return;
|
|
suppressClick = false;
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
event.stopImmediatePropagation();
|
|
};
|
|
|
|
const onTouchCancel = (event: Event) => cancel(event);
|
|
|
|
element.addEventListener('pointerdown', onPointerDown);
|
|
element.addEventListener('pointermove', onPointerMove);
|
|
element.addEventListener('pointerup', onPointerUp);
|
|
element.addEventListener('pointercancel', cancel);
|
|
element.addEventListener('lostpointercapture', cancel);
|
|
element.addEventListener('click', onClickCapture, true);
|
|
element.addEventListener('mousedown', stopMapGesture);
|
|
element.addEventListener('touchstart', stopMapGesture, { passive: true });
|
|
element.addEventListener('touchmove', stopMapGesture, { passive: true });
|
|
element.addEventListener('touchend', stopMapGesture, { passive: true });
|
|
element.addEventListener('touchcancel', onTouchCancel, { passive: true });
|
|
element.style.touchAction = 'none';
|
|
element.style.userSelect = 'none';
|
|
|
|
return () => {
|
|
cancel();
|
|
element.removeEventListener('pointerdown', onPointerDown);
|
|
element.removeEventListener('pointermove', onPointerMove);
|
|
element.removeEventListener('pointerup', onPointerUp);
|
|
element.removeEventListener('pointercancel', cancel);
|
|
element.removeEventListener('lostpointercapture', cancel);
|
|
element.removeEventListener('click', onClickCapture, true);
|
|
element.removeEventListener('mousedown', stopMapGesture);
|
|
element.removeEventListener('touchstart', stopMapGesture);
|
|
element.removeEventListener('touchmove', stopMapGesture);
|
|
element.removeEventListener('touchend', stopMapGesture);
|
|
element.removeEventListener('touchcancel', onTouchCancel);
|
|
element.style.touchAction = originalTouchAction;
|
|
element.style.userSelect = originalUserSelect;
|
|
};
|
|
}
|