UNPKG

goodtap

Version:

Tap, Press, Swipe events without all the event handlers

378 lines 17.5 kB
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