goodtap
Version:
Tap, Press, Swipe events without all the event handlers
378 lines • 17.5 kB
JavaScript
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
var VERSION = "0.2.1";
import { Dictionary, Vec2, Timer, List } from "goodcore";
import { until } from "goodcore/Arr";
import { newUUID } from "goodcore/Util";
import { is, findAll, get } from "goodcore/Dom";
import { isNotUndefined } from "goodcore/Test";
export var ALL_EVENTS = ["down", "drag", "up", "press", "tap", "swipe", "outside", "dragstart", "dragend", "drag"];
var GoodTap = (function () {
function GoodTap(rootElement, config) {
this.version = VERSION;
this.minSwipeDistance = 100;
this.maxTapDuration = 400;
this.defaultLongPressDuration = 400;
this.defaultDragResistance = 0;
this.dragResistanceSquared = 0;
this.downEvents = ["down"];
this.upEvents = ["up", "tap", "swipe"];
this.longPressIntervals = new Dictionary();
this.eventAttr = "";
this.upEventsAndPress = [];
this.index = 0;
this.lastInsides = new List();
this.dragging = new List();
this.isListeningToMovement = false;
this.config = { map: {} };
this.upEventsAndPress = this.upEvents.concat(["press"]);
this.init(rootElement || document.body, config);
}
GoodTap.prototype.init = function (rootElement, config) {
var _this = this;
this.config = __assign({}, this.config, config);
this.eventAttr = ALL_EVENTS.map(function (name) { return "[" + (_this.config.map[name] || name) + "]"; }).join(",");
this.downEvents = this.downEvents.map(function (name) { return (_this.config.map[name] || name); });
this.upEventsAndPress = this.upEventsAndPress.map(function (name) { return (_this.config.map[name] || name); });
if (this.hasTouchEvent()) {
rootElement.addEventListener("touchstart", function (ev) { _this.start(ev, rootElement); });
rootElement.addEventListener("touchend", function (ev) { _this.end(ev, rootElement); });
}
rootElement.addEventListener("mousedown", function (ev) { _this.start(ev, rootElement); });
rootElement.addEventListener("mouseup", function (ev) { _this.end(ev, rootElement); });
rootElement.addEventListener("focus", function (ev) { return _this.triggerOutside(ev.target, ev, rootElement); }, true);
this.root = rootElement;
};
GoodTap.prototype.findTarget = function (el) {
var target = null;
while (el && el.parentElement !== document && target === null) {
if (is(this.eventAttr, el)) {
target = el;
}
el = el.parentElement;
}
return target;
};
GoodTap.prototype.findTargets = function (el) {
var targets = [];
while (el && el.parentElement !== document) {
if (!el.id) {
el.id = newUUID();
}
if (is(this.eventAttr, el)) {
targets.push(el);
}
el = el.parentElement;
}
return targets;
};
GoodTap.prototype.getTouchPos = function (ev, pos) {
pos = pos || new Vec2(0, 0);
if (ev instanceof TouchEvent) {
pos.x = ev.changedTouches[0].pageX;
pos.y = ev.changedTouches[0].pageY;
}
else if (ev instanceof MouseEvent) {
pos.x = ev.pageX;
pos.y = ev.pageY;
}
return pos;
};
GoodTap.prototype.longPress = function (ev, target) {
var result = true;
var touchInfo = target.touchInfo;
result = this.executeAction(ev, target, this.mapEvent("press"), touchInfo);
if (result === false || target.hasAttribute("once")) {
clearInterval(this.longPressIntervals.get(touchInfo.index));
this.longPressIntervals.delete(touchInfo.index);
}
};
GoodTap.prototype.triggerOutside = function (target, ev, rootElement) {
var _this = this;
var outside = new List(findAll("[" + this.mapEvent("outside") + "]", this.root));
if (outside.length > 0) {
var insides_1 = new List(this.findTargets(target));
var preventOutside = insides_1.contains(function (el) { return el.hasAttribute("preventDefault"); });
if (!preventOutside) {
outside
.filter(function (el) { return _this.lastInsides.contains(el) && !insides_1.contains(el); })
.forEach(function (el) { return _this.handleEvent(_this.mapEvent("outside"), ev, el); });
this.lastInsides = insides_1;
}
}
};
GoodTap.prototype.move = function (ev, rootElement, target, action) {
var result = true;
var touchInfo = target.touchInfo;
if (touchInfo === undefined) {
this.end(ev, rootElement);
return;
}
if (ev.cancelBubble === true) {
return;
}
this.getTouchPos(ev, touchInfo.pos);
if (touchInfo.dragResistance === 0 ||
this.getTouchPos(ev).subtract(touchInfo.pos).lengthSq() < touchInfo.dragResistance) {
var dragTarget = target.touchInfo.dragTarget || target;
var hasDragTarget = target !== dragTarget;
touchInfo.dragResistance = 0;
try {
if (!!action) {
if (action === "[fn]" && ("drag-fn") in target) {
result = target["drag-fn"](ev, dragTarget, touchInfo);
}
else {
target["drag-fn"] = (new Function("event", "target", "touch", action)).bind(target);
result = target["drag-fn"](ev, dragTarget, touchInfo);
}
}
}
catch (err) {
throw name + " event function error on element '" + target.id + "'\n" + err.toString();
}
if (result === false) {
this.end(ev, rootElement);
}
if (dragTarget.hasAttribute("draggable")) {
var bcr = dragTarget.getBoundingClientRect();
dragTarget.style.left = ((hasDragTarget ? target.touchInfo.dragTargetOrigin.x : target.touchInfo.origin.x) + (target.touchInfo.pos.x - target.touchInfo.startPos.x)) + "px";
dragTarget.style.top = ((hasDragTarget ? target.touchInfo.dragTargetOrigin.y : target.touchInfo.origin.y) + (target.touchInfo.pos.y - target.touchInfo.startPos.y)) + "px";
}
}
if (target.hasAttribute("stopPropagation") || target.hasAttribute("gt-false")) {
ev.stopPropagation();
target.touchInfo.prevented.drag = true;
}
if (target.hasAttribute("preventDefault") || target.hasAttribute("gt-false") || target.hasAttribute("noTouchScroll")) {
ev.preventDefault();
}
};
GoodTap.prototype.start = function (ev, rootElement) {
var _this = this;
this.longPressIntervals.values.forEach(function (long) { return clearInterval(long); });
this.longPressIntervals.clear();
var preventDefault = false;
var stopPropagation = false;
var target = ev.target;
var loopCounter = 0;
this.triggerOutside(target, ev, rootElement);
while (loopCounter < 100 && (target = this.findTarget(target)) && !stopPropagation) {
++loopCounter;
var pressInterval = null;
if (target.hasAttribute(this.mapEvent("press"))) {
pressInterval = setInterval((function (target) { return _this.longPress(ev, target); }).bind(this, target), parseInt(target.getAttribute("pressInterval")) || this.defaultLongPressDuration);
}
var moveHandler = undefined;
var dragResistance = 0;
var hasDragTarget = false;
if (target.hasAttribute(this.mapEvent("drag"))) {
hasDragTarget = !!target.getAttribute("dragTarget");
dragResistance = parseInt(target.getAttribute("dragResistance"));
if (isNaN(dragResistance)) {
dragResistance = this.defaultDragResistance;
}
dragResistance *= dragResistance;
var dragAction = target.getAttribute(this.mapEvent("drag"));
moveHandler = (function (t, d, ev) {
_this.move(ev, rootElement, t, d);
}).bind(this, target, dragAction);
if (this.hasTouchEvent()) {
rootElement.addEventListener("touchmove", moveHandler);
}
rootElement.addEventListener("mousemove", moveHandler);
}
target.classList.add("gt-active");
var dragTarget = hasDragTarget ? get(target.getAttribute("dragTarget")) : target;
target.touchInfo = {
index: this.index++,
time: Timer.now(),
pos: this.getTouchPos(ev),
startPos: this.getTouchPos(ev),
origin: new Vec2(parseInt(target.style.left || "0"), parseInt(target.style.top || "0")),
long: pressInterval,
moveHandler: moveHandler,
dragResistance: dragResistance,
prevented: {},
dragTarget: hasDragTarget ? dragTarget : undefined,
dragTargetOrigin: hasDragTarget ? new Vec2(parseInt(dragTarget.style.left || "0"), parseInt(dragTarget.style.top || "0")) : undefined
};
if (target.hasAttribute(this.mapEvent("dragstart"))) {
this.handleEvent(this.mapEvent("dragstart"), ev, target);
}
if (pressInterval) {
this.longPressIntervals.set(target.touchInfo.index, target.touchInfo.long);
}
until(this.downEvents, function (name) {
if (target.hasAttribute(name)) {
stopPropagation = (_this.handleEvent(name, ev, target) === false);
if (!stopPropagation && target.hasAttribute("stopPropagation") || target.hasAttribute("gt-false")) {
stopPropagation = true;
ev.stopPropagation();
target.touchInfo.prevented[name] = true;
}
if (target.hasAttribute("preventDefault") || target.hasAttribute("gt-false")) {
ev.preventDefault();
preventDefault = true;
}
}
return stopPropagation;
});
target = target.parentElement;
}
};
GoodTap.prototype.isSwipe = function (ev, target) {
var pos = this.getTouchPos(ev);
var result = false;
if ("touchInfo" in target) {
var dX = pos.x - target.touchInfo.pos.x;
var dY = pos.y - target.touchInfo.pos.y;
var absDX = Math.abs(dX);
var absDY = Math.abs(dY);
var horizontal = absDX > absDY;
var distance = horizontal ? absDX : absDY;
result = distance >= this.minSwipeDistance;
if (result) {
target.touchInfo.swipeInfo = {
direction: horizontal ?
(dX < 0 ? "left" : "right") :
(dY < 0 ? "up" : "down"),
distance: distance,
delta: new Vec2(dX, dY)
};
}
}
return result;
};
GoodTap.prototype.end = function (ev, rootElement) {
var _this = this;
var time = Timer.now();
var stopPropagation = false;
var target = ev.target;
var loopCounter = 0;
var endedOutsideTargets = new List();
var originalTarget = target;
this.dragging.clear();
var _loop_1 = function () {
++loopCounter;
var touchInfo = target.touchInfo;
if (touchInfo !== undefined) {
var duration_1 = time - touchInfo.time;
until(this_1.upEventsAndPress, function (name) {
if (target.hasAttribute(name)) {
var isSwipe = _this.isSwipe(ev, target);
if ((name === _this.mapEvent("swipe") && isSwipe)
|| (name === _this.mapEvent("tap") && !isSwipe && duration_1 < _this.maxTapDuration)
|| (name === _this.mapEvent("up"))) {
stopPropagation = (_this.handleEvent(name, ev, target) === false);
}
if (stopPropagation || target.hasAttribute("stopPropagation") || target.hasAttribute("gt-false")) {
stopPropagation = true;
ev.stopPropagation();
delete target.touchInfo;
}
if (target.hasAttribute("preventDefault") || target.hasAttribute("gt-false")) {
ev.preventDefault();
}
}
return stopPropagation;
});
}
target = target.parentElement;
};
var this_1 = this;
while (loopCounter < 100 && (target = this.findTarget(target)) && !stopPropagation) {
_loop_1();
}
findAll(".gt-active").forEach(function (el) {
el.classList.remove("gt-active");
if (el.touchInfo !== undefined && el.touchInfo.moveHandler !== undefined) {
rootElement.removeEventListener("touchmove", el.touchInfo.moveHandler);
rootElement.removeEventListener("mousemove", el.touchInfo.moveHandler);
_this.handleEvent(_this.mapEvent("dragend"), ev, el);
}
delete el.touchInfo;
});
this.longPressIntervals.values.forEach(function (long) { return clearInterval(long); });
this.longPressIntervals.clear();
};
GoodTap.prototype.mapEvent = function (event) {
var result = event;
if (isNotUndefined(this.config.map[event])) {
result = this.config.map[event];
}
return result;
};
GoodTap.prototype.executeAction = function (ev, target, eventName, touchInfo) {
var result = true;
if (eventName === this.mapEvent("outside") || !touchInfo.prevented[eventName]) {
var attr = this.mapEvent(eventName);
var action = target.getAttribute(attr);
try {
if (action === "[fn]" && (attr + "-fn") in target) {
result = target[attr + "-fn"](ev, target, touchInfo);
}
else {
result = (new Function("event", "target", "touch", action)).bind(target)(ev, target, touchInfo);
}
}
catch (err) {
throw name + " event function error on element '" + target.id + "'\n" + err.toString();
}
}
return result;
};
GoodTap.prototype.handleEvent = function (name, ev, target) {
var result = true;
if (target) {
result = this.executeAction(ev, target, name, target.touchInfo);
if (name in this.upEvents) {
target.classList.remove("gt-active");
if (this.longPressIntervals.has(target.touchInfo.index)) {
clearInterval(this.longPressIntervals.get(target.touchInfo.index));
}
delete target.touchInfo;
}
}
return result;
};
GoodTap.prototype.on = function (element, name, fn) {
element.setAttribute(name, "[fn]");
element[name + "-fn"] = fn;
};
GoodTap.prototype.off = function (element, name) {
element.removeAttribute(name + "-action");
delete element[name + "-fn"];
};
GoodTap.prototype.hideKeyboard = function () {
var field = document.createElement("input");
field.setAttribute("type", "text");
document.body.appendChild(field);
setTimeout(function () {
field.focus();
setTimeout(function () {
field.setAttribute("style", "display:none;");
field.parentElement.removeChild(field);
}, 50);
}, 50);
};
GoodTap.prototype.hasTouchEvent = function () {
return isNotUndefined(document.documentElement) && "ontouchstart" in document.documentElement;
};
GoodTap.prototype.outside = function () {
this.triggerOutside(this.root, new FocusEvent(""));
this.hideKeyboard();
};
return GoodTap;
}());
export { GoodTap };
export function init(root, config) {
return new GoodTap(root, config);
}
//# sourceMappingURL=index.js.map