@figliolia/drag-detector
Version:
Mouse and Touch driven drag detection for DOM elements
166 lines (165 loc) • 5.22 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DragDetector = void 0;
/**
* DragDetector
*
* Given an x/y Threshold, detects mouse and touch driven
* drags on the target element. An element is targeted by
* calling:
*
* ```typescript
* const node = document.getElementById("myElement");
* const detector = new DragDetector({
* callback: ({ xDelta, rect, node }) => {
* const translate = parseInt(node.style.translate.slice(0, -2));
* const next = Math.max(0, Math.min(translate + xDelta, rect.width));
* node.style.translate = next;
* }
* });
* detector.register(node);
* node.addEventListener("mousedown", detector.onMouseDown);
* node.addEventListener("touchstart", detector.onMouseDown);
* ```
*/
class DragDetector {
constructor(options) {
this.active = false;
this.startX = 0;
this.startY = 0;
this.xDelta = 0;
this.yDelta = 0;
this.currentX = 0;
this.currentY = 0;
/**
* register
*
* Registers a DOM node to target for the `DragDetector`
* instance
*/
this.register = (node) => {
this.node = node;
};
this.onMouseDown = (e) => {
this.yDelta = 0;
this.xDelta = 0;
this.currentX = 0;
this.currentY = 0;
if (!this.node) {
return;
}
const [x, y] = this.mouseCoordinates(this.node, e);
const { xThreshold, yThreshold } = this.options;
if (x <= xThreshold && y <= yThreshold) {
this.startX = x;
this.startY = y;
this.currentX = x;
this.currentY = y;
this.active = true;
this.listen();
}
};
this.onMouseMove = (e) => {
if (!this.active || !this.node) {
return;
}
const [x, y, rect] = this.mouseCoordinates(this.node, e);
this.lastRect = rect;
this.xDelta = x - this.currentX;
this.yDelta = y - this.currentY;
this.options.callback({
x,
y,
rect,
node: this.node,
xDelta: this.xDelta,
yDelta: this.yDelta,
xDistance: x - this.startX,
yDistance: y - this.startY,
});
this.currentX = x;
this.currentY = y;
};
this.onMouseUp = () => {
this.options.callback({
exit: true,
node: this.node,
x: this.currentX,
y: this.currentY,
xDelta: this.yDelta,
yDelta: this.xDelta,
rect: this.lastRect,
xDistance: this.currentX - this.startX,
yDistance: this.currentY - this.startY,
});
this.destroy();
};
/**
* bindings
*
* Bindings for React users to attach their instances to DOM
* elements
* ```tsx
* const Component = () => {
* const detector = useDragDetector(options);
* return (
* <div {...detector.bindings} />
* );
* }
* ```
*/
this.bindings = {
ref: this.register,
onMouseDown: this.onMouseDown,
onTouchStart: this.onMouseDown,
};
this.options = DragDetector.mergeDefaultOptions(options);
}
/**
* setOptions
*
* Allows users to update options on the fly
*/
setOptions(options) {
this.options = DragDetector.mergeDefaultOptions(options);
}
/**
* destroy
*
* Cleans up the `DragDetector` instance and event listeners
*/
destroy() {
this.active = false;
this.lastRect = undefined;
document.removeEventListener("mousemove", this.onMouseMove);
document.removeEventListener("mouseup", this.onMouseUp);
document.removeEventListener("touchmove", this.onMouseMove);
document.removeEventListener("touchend", this.onMouseUp);
}
mouseCoordinates(node, e) {
let clientX;
let clientY;
if ("touches" in e) {
({ clientX, clientY } = e.touches[0]);
}
else {
({ clientX, clientY } = e);
}
const rect = this.lastRect || node.getBoundingClientRect();
return [clientX - rect.left, clientY - rect.top, rect];
}
listen() {
document.addEventListener("mousemove", this.onMouseMove, { passive: true });
document.addEventListener("mouseup", this.onMouseUp, { passive: true });
document.addEventListener("touchmove", this.onMouseMove, { passive: true });
document.addEventListener("touchend", this.onMouseUp, { passive: true });
}
static mergeDefaultOptions(options) {
return Object.assign({}, this.defaultOptions, options);
}
}
exports.DragDetector = DragDetector;
DragDetector.defaultOptions = {
xThreshold: Infinity,
yThreshold: Infinity,
};