dragjs
Version:
Simple utility to make it easier to implement drag based things (ie. sliders and such)
216 lines (215 loc) • 8.02 kB
JavaScript
/// <reference lib="dom" />
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.xyslider = exports.slider = exports.draggable = void 0;
function draggable({ element, handle, xPosition }, cbs) {
if (!element) {
console.warn("drag is missing elem!");
return;
}
dragTemplate(element, "touchstart", "touchmove", "touchend", cbs, handle, xPosition);
dragTemplate(element, "mousedown", "mousemove", "mouseup", cbs, handle, xPosition);
}
exports.draggable = draggable;
function xyslider(o) {
const twod = div(o["class"] || "", o.parent);
const pointer = div("pointer", twod);
div("shape shape1", pointer);
div("shape shape2", pointer);
div("bg bg1", twod);
div("bg bg2", twod);
draggable({ element: twod }, attachPointer(o.cbs, pointer));
return {
background: twod,
pointer,
};
}
exports.xyslider = xyslider;
function slider(o) {
const oned = div(o["class"], o.parent);
const pointer = div("pointer", oned);
div("shape", pointer);
div("bg", oned);
draggable({ element: oned }, attachPointer(o.cbs, pointer));
return {
background: oned,
pointer: pointer,
};
}
exports.slider = slider;
function attachPointer(cbs, pointer) {
const ret = {};
Object.entries(cbs).forEach(([name, callback]) => {
ret[name] = (p) => {
callback({ ...p, pointer });
};
});
return ret;
}
// move to elemutils lib?
function div(klass, p) {
return e("div", klass, p);
}
function e(type, klass, p) {
const elem = document.createElement(type);
if (klass) {
elem.className = klass;
}
p.appendChild(elem);
return elem;
}
function dragTemplate(elem, down, move, up, cbs, handle, xPosition) {
cbs = getCbs(cbs, xPosition);
const beginCb = cbs.begin;
const changeCb = cbs.change;
const endCb = cbs.end;
on(handle || elem, down, (e) => {
// @ts-ignore Figure out how to type this
const moveHandler = (e) => callCb(changeCb, elem, e);
function upHandler() {
off(document, move, moveHandler);
off(document, up, upHandler);
// @ts-ignore Figure out how to type this
callCb(endCb, elem, e);
}
on(document, move, moveHandler);
on(document, up, upHandler);
// @ts-ignore Figure out how to type this
callCb(beginCb, elem, e);
});
}
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
function on(elem, evt, handler) {
// Test via a getter in the options object to see if the passive property is accessed
let supportsPassive = false;
try {
const opts = Object.defineProperty({}, "passive", {
get: function () {
supportsPassive = true;
return undefined;
},
});
// @ts-ignore Deno doesn't know this
// deno-shim-ignore
globalThis.addEventListener("testPassive", null, opts);
// @ts-ignore Deno doesn't know this
// deno-shim-ignore
globalThis.removeEventListener("testPassive", null, opts);
}
catch (err) {
console.error(err);
}
elem.addEventListener(evt, handler, supportsPassive ? { passive: false } : false);
}
function off(elem, evt, handler) {
elem.removeEventListener(evt, handler, false);
}
function getCbs(cbs, xPosition = "left") {
let initialOffset;
let initialPos;
const defaultCbs = {
begin: (c) => {
const bodyWidth = document.body.clientWidth;
initialOffset = {
x: xPosition === "left"
? c.elem.offsetLeft
: bodyWidth - c.elem.offsetLeft - c.elem.clientWidth,
y: c.elem.offsetTop,
};
initialPos = xPosition === "left"
? c.cursor
: { x: bodyWidth - c.cursor.x, y: c.cursor.y };
},
change: (c) => {
if (typeof initialOffset.x !== "number" || typeof c.cursor.x !== "number" ||
typeof initialPos.x !== "number") {
return;
}
const bodyWidth = document.body.clientWidth;
style(c.elem, xPosition, xPosition === "left"
? (initialOffset.x + c.cursor.x - initialPos.x) + "px"
: (initialOffset.x + (bodyWidth - c.cursor.x) - initialPos.x) + "px");
if (typeof initialOffset.y !== "number" || typeof c.cursor.y !== "number" ||
typeof initialPos.y !== "number") {
return;
}
style(c.elem, "top", (initialOffset.y + c.cursor.y - initialPos.y) + "px");
},
end: () => { },
};
if (cbs) {
return {
begin: cbs.begin
? ((args) => {
const ret = cbs.begin && cbs.begin(args);
if (ret) {
// @ts-ignore This is fine
return defaultCbs.begin(args);
}
})
: defaultCbs.begin,
change: cbs.change
? ((args) => {
const ret = cbs.change && cbs.change(args);
if (ret) {
// @ts-ignore This is fine
return defaultCbs.change(args);
}
})
: defaultCbs.change,
end: cbs.end || defaultCbs.end,
};
}
return defaultCbs;
}
function style(e, prop, value) {
// @ts-ignore https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style#setting_styles
e.style[prop] = value;
}
function callCb(cb, elem, e) {
e.preventDefault();
// http://www.quirksmode.org/js/findpos.html
const offset = elem.getBoundingClientRect();
const width = elem.clientWidth;
const height = elem.clientHeight;
const cursor = {
x: cursorX(elem, e),
y: cursorY(elem, e),
};
if (typeof cursor.x !== "number" || typeof cursor.y !== "number") {
return;
}
const x = (cursor.x - offset.left) / width;
const y = (cursor.y - offset.top) / height;
cb({
x: isNaN(x) ? 0 : x,
y: isNaN(y) ? 0 : y,
cursor: cursor,
elem,
e,
});
}
// http://javascript.about.com/library/blmousepos.htm
function cursorX(_elem, evt) {
// https://github.com/bebraw/dragjs/issues/8
if (window.TouchEvent && evt instanceof window.TouchEvent) {
return evt.touches.item(0)?.clientX;
}
return evt.clientX;
}
function cursorY(_elem, evt) {
// https://github.com/bebraw/dragjs/issues/8
if (window.TouchEvent && evt instanceof window.TouchEvent) {
return evt.touches.item(0)?.clientY;
}
return evt.clientY;
}
});