vue3-touch-events
Advanced tools
Comparing version 5.0.10 to 5.0.11
@@ -1,379 +0,511 @@ | ||
// index.js | ||
/** | ||
* @project vue3-touch-events | ||
* @author Robin Rodricks, Xavier Julien, Jerry Bendy | ||
* @since 30/4/2021 | ||
* @url https://github.com/robinrodricks/vue3-touch-events | ||
*/ | ||
// Helper function to get X coordinate from touch/mouse event | ||
function touchX(event) { | ||
if (event.type.indexOf("mouse") !== -1) { | ||
return event.clientX; | ||
} | ||
return event.touches?.[0]?.clientX ?? 0; | ||
var _a, _b, _c; | ||
if (event.type.indexOf('mouse') !== -1) { | ||
return event.clientX; | ||
} | ||
return (_c = (_b = (_a = event.touches) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.clientX) !== null && _c !== void 0 ? _c : 0; | ||
} | ||
// Helper function to get Y coordinate from touch/mouse event | ||
function touchY(event) { | ||
if (event.type.indexOf("mouse") !== -1) { | ||
return event.clientY; | ||
} | ||
return event.touches?.[0]?.clientY ?? 0; | ||
var _a, _b, _c; | ||
if (event.type.indexOf('mouse') !== -1) { | ||
return event.clientY; | ||
} | ||
return (_c = (_b = (_a = event.touches) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.clientY) !== null && _c !== void 0 ? _c : 0; | ||
} | ||
var isPassiveSupported = function() { | ||
var supportsPassive = false; | ||
try { | ||
var opts = Object.defineProperty({}, "passive", { | ||
get: function() { | ||
supportsPassive = true; | ||
} | ||
}); | ||
window.addEventListener("test", null, opts); | ||
} catch (e) { | ||
} | ||
return supportsPassive; | ||
}(); | ||
var vueTouchEvents = { | ||
install: function(app, constructorOptions) { | ||
var globalOptions = Object.assign({}, { | ||
touchClass: "", | ||
namespace: "touch", | ||
disableClick: false, | ||
tapTolerance: 10, | ||
touchHoldTolerance: 400, | ||
longTapTimeInterval: 400, | ||
rollOverFrequency: 100, | ||
dragFrequency: 10, | ||
dragOutside: false, | ||
swipeTolerance: 100, | ||
swipeConeSize: 0.75, | ||
zoomFrequency: 10, | ||
zoomDistance: 10, | ||
zoomInOutDistance: 100 | ||
}, constructorOptions); | ||
function touchStartEvent(event) { | ||
var $this = this.$$touchObj, isTouchEvent = event.type.indexOf("touch") >= 0, isMouseEvent = event.type.indexOf("mouse") >= 0, $el = this; | ||
if (isTouchEvent) { | ||
$this.lastTouchStartTime = event.timeStamp; | ||
} | ||
if (isMouseEvent && $this.lastTouchStartTime && event.timeStamp - $this.lastTouchStartTime < 350) { | ||
return; | ||
} | ||
if ($this.touchStarted) { | ||
return; | ||
} | ||
addTouchClass(this); | ||
$this.touchStarted = true; | ||
$this.touchMoved = false; | ||
$this.swipeOutBounded = false; | ||
$this.isZooming = false; | ||
$this.startX = touchX(event); | ||
$this.startY = touchY(event); | ||
$this.currentX = 0; | ||
$this.currentY = 0; | ||
$this.touchStartTime = event.timeStamp; | ||
$this.hasSwipe = hasEvent($this, "swipe") || hasEvent($this, "swipe.left") || hasEvent($this, "swipe.right") || hasEvent($this, "swipe.top") || hasEvent($this, "swipe.bottom"); | ||
$this.hasZoom = hasEvent($this, "zoom") || hasEvent($this, "zoom.in") || hasEvent($this, "zoom.out"); | ||
if (hasEvent($this, "hold")) { | ||
$this.touchHoldTimer = setTimeout(function() { | ||
$this.touchHoldTimer = null; | ||
triggerEvent(event, $el, "hold"); | ||
}, $this.options.touchHoldTolerance); | ||
} | ||
triggerEvent(event, this, "press"); | ||
// Check for passive event listener support | ||
const isPassiveSupported = (function () { | ||
let supportsPassive = false; | ||
try { | ||
const opts = Object.defineProperty({}, 'passive', { | ||
get: function () { | ||
supportsPassive = true; | ||
return true; | ||
} | ||
}); | ||
window.addEventListener('test', null, opts); | ||
} | ||
function touchMoveEventWindow(event) { | ||
var $this = this.$$touchObj; | ||
if ($this.touchStarted == true) { | ||
touchMoveEvent(event, $this); | ||
} | ||
} | ||
function touchMoveEvent(event, $this = null) { | ||
if ($this == null) | ||
$this = this.$$touchObj; | ||
var curX = touchX(event); | ||
var curY = touchY(event); | ||
var movedAgain = $this.currentX != curX || $this.currentY != curY; | ||
$this.currentX = curX; | ||
$this.currentY = curY; | ||
if (!$this.touchMoved) { | ||
var tapTolerance = $this.options.tapTolerance; | ||
$this.touchMoved = Math.abs($this.startX - $this.currentX) > tapTolerance || Math.abs($this.startY - $this.currentY) > tapTolerance; | ||
if ($this.touchMoved) { | ||
cancelTouchHoldTimer($this); | ||
triggerEvent(event, $this.element, "drag.once"); | ||
catch (e) { } | ||
return supportsPassive; | ||
})(); | ||
// Main Vue Touch Events plugin | ||
const Vue3TouchEvents = { | ||
install(app, constructorOptions) { | ||
// Default global options merged with constructor options | ||
var globalOptions = Object.assign({}, { | ||
// CORE | ||
touchClass: '', | ||
namespace: 'touch', | ||
// CLICK/TAP | ||
disableClick: false, | ||
tapTolerance: 10, // px | ||
touchHoldTolerance: 400, // ms | ||
longTapTimeInterval: 400, // ms | ||
rollOverFrequency: 100, // ms | ||
// DRAG | ||
dragFrequency: 10, // ms | ||
dragOutside: false, | ||
// SWIPE | ||
swipeTolerance: 100, // px | ||
swipeConeSize: 0.75, // number between 0 to 1 | ||
// ZOOM | ||
zoomFrequency: 10, // ms | ||
zoomDistance: 10, // px | ||
zoomInOutDistance: 100, // px | ||
// NOTE: When adding props here, update `index.d.ts` as well!! | ||
}, constructorOptions); | ||
/** Fired when the user performs a MOUSE DOWN on the object */ | ||
function touchStartEvent(event) { | ||
var $this = this.$$touchObj, isTouchEvent = event.type.indexOf('touch') >= 0, isMouseEvent = event.type.indexOf('mouse') >= 0, $el = this; | ||
if (isTouchEvent) { | ||
$this.lastTouchStartTime = event.timeStamp; | ||
} | ||
if (isMouseEvent && $this.lastTouchStartTime && event.timeStamp - $this.lastTouchStartTime < 350) { | ||
return; | ||
} | ||
if ($this.touchStarted) { | ||
return; | ||
} | ||
addTouchClass(this); | ||
$this.touchStarted = true; // always true while the element is being PRESSED | ||
$this.touchMoved = false; // true only when the element is PRESSED and DRAGGED a bit | ||
$this.swipeOutBounded = false; | ||
$this.isZooming = false; | ||
$this.startX = touchX(event); | ||
$this.startY = touchY(event); | ||
$this.currentX = 0; // always updated with the last mouse X/Y while over the element | ||
$this.currentY = 0; | ||
$this.touchStartTime = event.timeStamp; | ||
// performance: only process swipe events if `swipe.*` event is registered on this element | ||
$this.hasSwipe = hasEvent($this, 'swipe') | ||
|| hasEvent($this, 'swipe.left') || hasEvent($this, 'swipe.right') | ||
|| hasEvent($this, 'swipe.top') || hasEvent($this, 'swipe.bottom'); | ||
// performance: only process zoom events if `zoom.*` event is registered on this element | ||
$this.hasZoom = hasEvent($this, 'zoom') || hasEvent($this, 'zoom.in') || hasEvent($this, 'zoom.out'); | ||
// performance: only start hold timer if the `hold` event is registered on this element | ||
if (hasEvent($this, 'hold')) { | ||
// Trigger touchhold event after `touchHoldTolerance` MS | ||
$this.touchHoldTimer = setTimeout(function () { | ||
$this.touchHoldTimer = null; | ||
triggerEvent(event, $el, 'hold'); | ||
}, $this.options.touchHoldTolerance); | ||
} | ||
triggerEvent(event, this, 'press'); | ||
} | ||
} else if ($this.hasSwipe && !$this.swipeOutBounded) { | ||
var swipeOutBounded = $this.options.swipeTolerance; | ||
if (Math.abs($this.startX - $this.currentX) / Math.abs($this.startY - $this.currentY) > $this.options.swipeConeSize && Math.abs($this.startY - $this.currentY) / Math.abs($this.startX - $this.currentX) > $this.options.swipeConeSize) { | ||
$this.swipeOutBounded = Math.abs($this.startY - $this.currentY) < swipeOutBounded && Math.abs($this.startX - $this.currentX) < swipeOutBounded; | ||
/** | ||
Fired when the user MOVES the mouse over the window. | ||
*/ | ||
function touchMoveEventWindow(event) { | ||
// only process if pressed | ||
var $this = this.$$touchObj; | ||
if ($this.touchStarted == true) { | ||
// process event and pass 'this' onward | ||
touchMoveEvent(event, $this); | ||
} | ||
} | ||
} | ||
if (movedAgain && hasEvent($this, "rollover")) { | ||
var now = event.timeStamp; | ||
if ($this.touchRollTime == null || now > $this.touchRollTime + $this.options.rollOverFrequency) { | ||
$this.touchRollTime = now; | ||
triggerEvent(event, $this.element, "rollover"); | ||
/** | ||
Fired when the user DRAGS the object or MOVES the mouse over the object. | ||
*/ | ||
function touchMoveEvent(event, $this = null) { | ||
if ($this == null) | ||
$this = this.$$touchObj; | ||
var curX = touchX(event); | ||
var curY = touchY(event); | ||
var movedAgain = ($this.currentX != curX) || ($this.currentY != curY); | ||
$this.currentX = curX; | ||
$this.currentY = curY; | ||
//-------------------------------------------------------------------------------------- | ||
// DRAG ONCE | ||
//-------------------------------------------------------------------------------------- | ||
if (!$this.touchMoved) { | ||
var tapTolerance = $this.options.tapTolerance; | ||
$this.touchMoved = Math.abs($this.startX - $this.currentX) > tapTolerance || | ||
Math.abs($this.startY - $this.currentY) > tapTolerance; | ||
// trigger `drag.once` only once after mouse FIRST moved while dragging the element | ||
// (`touchMoved` is the flag that indicates we no longer need to trigger this) | ||
if ($this.touchMoved) { | ||
cancelTouchHoldTimer($this); | ||
triggerEvent(event, $this.element, 'drag.once'); | ||
} | ||
} | ||
//-------------------------------------------------------------------------------------- | ||
// SWIPE | ||
//-------------------------------------------------------------------------------------- | ||
// performance: only process swipe events if `swipe.*` event is registered on this element | ||
else if ($this.hasSwipe && !$this.swipeOutBounded) { | ||
var swipeOutBounded = $this.options.swipeTolerance; | ||
// Process swipe events using cones | ||
if (Math.abs($this.startX - $this.currentX) / Math.abs($this.startY - $this.currentY) > $this.options.swipeConeSize && | ||
Math.abs($this.startY - $this.currentY) / Math.abs($this.startX - $this.currentX) > $this.options.swipeConeSize) { | ||
$this.swipeOutBounded = (Math.abs($this.startY - $this.currentY) < swipeOutBounded) && (Math.abs($this.startX - $this.currentX) < swipeOutBounded); | ||
} | ||
} | ||
//-------------------------------------------------------------------------------------- | ||
// ROLL OVER | ||
//-------------------------------------------------------------------------------------- | ||
// only trigger `rollover` event if cursor actually moved over this element | ||
if (movedAgain && hasEvent($this, 'rollover')) { | ||
// throttle the `rollover` event based on `rollOverFrequency` | ||
var now = event.timeStamp; | ||
if ($this.touchRollTime == null || now > ($this.touchRollTime + $this.options.rollOverFrequency)) { | ||
$this.touchRollTime = now; | ||
triggerEvent(event, $this.element, 'rollover'); | ||
} | ||
} | ||
//-------------------------------------------------------------------------------------- | ||
// DRAG | ||
//-------------------------------------------------------------------------------------- | ||
// only trigger `drag` event if cursor actually moved and if we are still dragging this element | ||
if ($this.touchStarted && $this.touchMoved && movedAgain && hasEvent($this, 'drag')) { | ||
// throttle the `drag` event based on `dragFrequency` | ||
var now = event.timeStamp; | ||
if ($this.touchDragTime == null || now > ($this.touchDragTime + $this.options.dragFrequency)) { | ||
$this.touchDragTime = now; | ||
triggerEvent(event, $this.element, 'drag'); | ||
} | ||
} | ||
//-------------------------------------------------------------------------------------- | ||
// ZOOM | ||
//-------------------------------------------------------------------------------------- | ||
// only trigger `zoom` event if cursor actually moved | ||
if ($this.touchStarted && $this.hasZoom) { | ||
// throttle the `zoom` event based on `zoomFrequency` | ||
var now = event.timeStamp; | ||
if ($this.touchZoomTime == null || now > ($this.touchZoomTime + $this.options.zoomFrequency)) { | ||
$this.touchZoomTime = now; | ||
checkZoom($this, event); | ||
} | ||
} | ||
} | ||
} | ||
if ($this.touchStarted && $this.touchMoved && movedAgain && hasEvent($this, "drag")) { | ||
var now = event.timeStamp; | ||
if ($this.touchDragTime == null || now > $this.touchDragTime + $this.options.dragFrequency) { | ||
$this.touchDragTime = now; | ||
triggerEvent(event, $this.element, "drag"); | ||
function checkZoom($this, event) { | ||
// get the list of changed touches from the event | ||
const touches = event.changedTouches; | ||
// check if exactly two fingers are being used | ||
if (touches.length !== 2) { | ||
// reset dragging state if fewer or more than 2 touches are detected | ||
$this.isZooming = false; | ||
return; | ||
} | ||
// calculate the distance between the two touch points (euclidean distance) | ||
const newDistance = Math.sqrt(Math.pow(touches[0].clientX - touches[1].clientX, 2) + // horizontal distance | ||
Math.pow(touches[0].clientY - touches[1].clientY, 2) // vertical distance | ||
); | ||
// initialize the gesture if it's not already active | ||
if (!$this.isZooming) { | ||
// mark the gesture as active and store the initial distance | ||
$this.isZooming = true; | ||
$this.initialZoomDistance = newDistance; | ||
return; | ||
} | ||
// calculate the zoom factor based on the change in distance | ||
const zoomFactor = newDistance / $this.initialZoomDistance; | ||
//-------------------------------------------------------------------------------------- | ||
// ZOOM | ||
//-------------------------------------------------------------------------------------- | ||
if (hasEvent($this, 'zoom')) { | ||
// check if the zoom factor exceeds the threshold for zooming | ||
if (Math.abs(zoomFactor - 1) > ($this.options.zoomDistance / $this.initialZoomDistance)) { | ||
// trigger the zoom callback with the source and zoom factor | ||
triggerEvent(event, $this.element, 'zoom', zoomFactor); | ||
} | ||
} | ||
//-------------------------------------------------------------------------------------- | ||
// ZOOM IN/OUT | ||
//-------------------------------------------------------------------------------------- | ||
if (hasEvent($this, 'zoom.in') || hasEvent($this, 'zoom.out')) { | ||
// check if the distance change is significant enough to count as a zoom gesture | ||
if (Math.abs(newDistance - $this.initialZoomDistance) > $this.options.zoomInOutDistance) { | ||
// determine zoom direction | ||
if (newDistance > $this.initialZoomDistance) { | ||
// fingers moved apart = zoom in | ||
triggerEvent(event, $this.element, 'zoom.in'); | ||
} | ||
else { | ||
// fingers moved closer = zoom out | ||
triggerEvent(event, $this.element, 'zoom.out'); | ||
} | ||
} | ||
} | ||
// reset the dragging state after detecting the pinch gesture | ||
$this.isZooming = false; | ||
} | ||
} | ||
if ($this.touchStarted && $this.hasZoom) { | ||
var now = event.timeStamp; | ||
if ($this.touchZoomTime == null || now > $this.touchZoomTime + $this.options.zoomFrequency) { | ||
$this.touchZoomTime = now; | ||
checkZoom($this, event); | ||
function touchCancelEvent() { | ||
var $this = this.$$touchObj; | ||
if ($this.touchStarted == true) { | ||
cancelTouchHoldTimer($this); | ||
removeTouchClass(this); | ||
$this.touchStarted = $this.touchMoved = false; | ||
$this.startX = $this.startY = 0; | ||
} | ||
} | ||
} | ||
} | ||
function checkZoom($this, event) { | ||
const touches = event.changedTouches; | ||
if (touches.length !== 2) { | ||
$this.isZooming = false; | ||
return; | ||
} | ||
const newDistance = Math.sqrt(Math.pow(touches[0].clientX - touches[1].clientX, 2) + Math.pow(touches[0].clientY - touches[1].clientY, 2)); | ||
if (!$this.isZooming) { | ||
$this.isZooming = true; | ||
$this.initialZoomDistance = newDistance; | ||
return; | ||
} | ||
const zoomFactor = newDistance / $this.initialZoomDistance; | ||
if (hasEvent($this, "zoom")) { | ||
if (Math.abs(zoomFactor - 1) > $this.options.zoomDistance / $this.initialZoomDistance) { | ||
triggerEvent(event, $this.element, "zoom", zoomFactor); | ||
/** Fired when the user performs a MOUSE UP on the object (releases the mouse button or finger press) */ | ||
function touchEndEvent(event) { | ||
var $this = this.$$touchObj; | ||
if ($this.touchStarted == true) { | ||
var isTouchEvent = event.type.indexOf('touch') >= 0; | ||
var isMouseEvent = event.type.indexOf('mouse') >= 0; | ||
if (isTouchEvent) { | ||
$this.lastTouchEndTime = event.timeStamp; | ||
} | ||
var touchholdEnd = isTouchEvent && !$this.touchHoldTimer; | ||
cancelTouchHoldTimer($this); | ||
$this.touchStarted = false; | ||
removeTouchClass(this); | ||
if (isMouseEvent && $this.lastTouchEndTime && event.timeStamp - $this.lastTouchEndTime < 350) { | ||
return; | ||
} | ||
//-------------------------------------------------------------------------------------- | ||
// RELEASE | ||
//-------------------------------------------------------------------------------------- | ||
// trigger `end` event when touch stopped | ||
triggerEvent(event, this, 'release'); | ||
//-------------------------------------------------------------------------------------- | ||
// LONGTAP / HOLD / TAP | ||
//-------------------------------------------------------------------------------------- | ||
if (!$this.touchMoved) { | ||
// detect if this is a longTap event or not | ||
if (hasEvent($this, 'longtap') && event.timeStamp - $this.touchStartTime > $this.options.longTapTimeInterval) { | ||
if (event.cancelable) { | ||
event.preventDefault(); | ||
} | ||
triggerEvent(event, this, 'longtap'); | ||
} | ||
else if (hasEvent($this, 'hold') && touchholdEnd) { | ||
if (event.cancelable) { | ||
event.preventDefault(); | ||
} | ||
return; | ||
} | ||
else { | ||
// emit tap event | ||
triggerEvent(event, this, 'tap'); | ||
} | ||
} | ||
//-------------------------------------------------------------------------------------- | ||
// SWIPE | ||
//-------------------------------------------------------------------------------------- | ||
// only process swipe events if `swipe.*` event is registered on this element | ||
else if ($this.hasSwipe && !$this.swipeOutBounded) { | ||
var swipeOutBounded = $this.options.swipeTolerance, direction, distanceY = Math.abs($this.startY - $this.currentY), distanceX = Math.abs($this.startX - $this.currentX); | ||
if (distanceY > swipeOutBounded || distanceX > swipeOutBounded) { | ||
// Check which swipe direction it is based on the mouse movement | ||
if (distanceX > swipeOutBounded) { | ||
direction = $this.startX > $this.currentX ? 'left' : 'right'; | ||
} | ||
else { | ||
direction = $this.startY > $this.currentY ? 'top' : 'bottom'; | ||
} | ||
// Only emit the specified event when it has modifiers | ||
if (hasEvent($this, 'swipe.' + direction)) { | ||
triggerEvent(event, this, 'swipe.' + direction, direction); | ||
} | ||
else { | ||
// Emit a common event when it has no any modifier | ||
triggerEvent(event, this, 'swipe', direction); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
if (hasEvent($this, "zoom.in") || hasEvent($this, "zoom.out")) { | ||
if (Math.abs(newDistance - $this.initialZoomDistance) > $this.options.zoomInOutDistance) { | ||
if (newDistance > $this.initialZoomDistance) { | ||
triggerEvent(event, $this.element, "zoom.in"); | ||
} else { | ||
triggerEvent(event, $this.element, "zoom.out"); | ||
} | ||
function mouseEnterEvent() { | ||
addTouchClass(this); | ||
} | ||
} | ||
$this.isZooming = false; | ||
} | ||
function touchCancelEvent() { | ||
var $this = this.$$touchObj; | ||
if ($this.touchStarted == true) { | ||
cancelTouchHoldTimer($this); | ||
removeTouchClass(this); | ||
$this.touchStarted = $this.touchMoved = false; | ||
$this.startX = $this.startY = 0; | ||
} | ||
} | ||
function touchEndEvent(event) { | ||
var $this = this.$$touchObj; | ||
if ($this.touchStarted == true) { | ||
var isTouchEvent = event.type.indexOf("touch") >= 0; | ||
var isMouseEvent = event.type.indexOf("mouse") >= 0; | ||
if (isTouchEvent) { | ||
$this.lastTouchEndTime = event.timeStamp; | ||
function mouseLeaveEvent() { | ||
removeTouchClass(this); | ||
} | ||
var touchholdEnd = isTouchEvent && !$this.touchHoldTimer; | ||
cancelTouchHoldTimer($this); | ||
$this.touchStarted = false; | ||
removeTouchClass(this); | ||
if (isMouseEvent && $this.lastTouchEndTime && event.timeStamp - $this.lastTouchEndTime < 350) { | ||
return; | ||
function hasEvent($this, eventType) { | ||
var callbacks = $this.callbacks[eventType]; | ||
return (callbacks != null && callbacks.length > 0); | ||
} | ||
triggerEvent(event, this, "release"); | ||
if (!$this.touchMoved) { | ||
if (hasEvent($this, "longtap") && event.timeStamp - $this.touchStartTime > $this.options.longTapTimeInterval) { | ||
if (event.cancelable) { | ||
event.preventDefault(); | ||
function triggerEvent(e, $el, eventType, param) { | ||
var $this = $el.$$touchObj; | ||
// get the subscribers for this event | ||
var callbacks = $this.callbacks[eventType]; | ||
// exit if no subscribers to this particular event | ||
if (callbacks == null || callbacks.length === 0) { | ||
return null; | ||
} | ||
triggerEvent(event, this, "longtap"); | ||
} else if (hasEvent($this, "hold") && touchholdEnd) { | ||
if (event.cancelable) { | ||
event.preventDefault(); | ||
// per callback | ||
for (var i = 0; i < callbacks.length; i++) { | ||
var binding = callbacks[i]; | ||
if (binding.modifiers.stop) { | ||
e.stopPropagation(); | ||
} | ||
if (binding.modifiers.prevent) { | ||
e.preventDefault(); | ||
} | ||
// handle `self` modifier` | ||
if (binding.modifiers.self && e.target !== e.currentTarget) { | ||
continue; | ||
} | ||
if (typeof binding.value === 'function') { | ||
if (param) { | ||
binding.value(param, e); | ||
} | ||
else { | ||
binding.value(e); | ||
} | ||
} | ||
} | ||
return; | ||
} else { | ||
triggerEvent(event, this, "tap"); | ||
} | ||
} else if ($this.hasSwipe && !$this.swipeOutBounded) { | ||
var swipeOutBounded = $this.options.swipeTolerance, direction, distanceY = Math.abs($this.startY - $this.currentY), distanceX = Math.abs($this.startX - $this.currentX); | ||
if (distanceY > swipeOutBounded || distanceX > swipeOutBounded) { | ||
if (distanceX > swipeOutBounded) { | ||
direction = $this.startX > $this.currentX ? "left" : "right"; | ||
} else { | ||
direction = $this.startY > $this.currentY ? "top" : "bottom"; | ||
} | ||
if (hasEvent($this, "swipe." + direction)) { | ||
triggerEvent(event, this, "swipe." + direction, direction); | ||
} else { | ||
triggerEvent(event, this, "swipe", direction); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
function mouseEnterEvent() { | ||
addTouchClass(this); | ||
} | ||
function mouseLeaveEvent() { | ||
removeTouchClass(this); | ||
} | ||
function hasEvent($this, eventType) { | ||
var callbacks = $this.callbacks[eventType]; | ||
return callbacks != null && callbacks.length > 0; | ||
} | ||
function triggerEvent(e, $el, eventType, param) { | ||
var $this = $el.$$touchObj; | ||
var callbacks = $this.callbacks[eventType]; | ||
if (callbacks == null || callbacks.length === 0) { | ||
return null; | ||
} | ||
for (var i = 0;i < callbacks.length; i++) { | ||
var binding = callbacks[i]; | ||
if (binding.modifiers.stop) { | ||
e.stopPropagation(); | ||
function addTouchClass($el) { | ||
var className = $el.$$touchObj.options.touchClass; | ||
className && $el.classList.add(className); | ||
} | ||
if (binding.modifiers.prevent) { | ||
e.preventDefault(); | ||
function removeTouchClass($el) { | ||
var className = $el.$$touchObj.options.touchClass; | ||
className && $el.classList.remove(className); | ||
} | ||
if (binding.modifiers.self && e.target !== e.currentTarget) { | ||
continue; | ||
function cancelTouchHoldTimer($this) { | ||
if ($this && $this.touchHoldTimer) { | ||
clearTimeout($this.touchHoldTimer); | ||
$this.touchHoldTimer = null; | ||
} | ||
} | ||
if (typeof binding.value === "function") { | ||
if (param) { | ||
binding.value(param, e); | ||
} else { | ||
binding.value(e); | ||
} | ||
function buildTouchObj($el, extraOptions) { | ||
var touchObj = $el.$$touchObj || { | ||
element: $el, | ||
// an object contains all callbacks registered, | ||
// key is event name, value is an array | ||
callbacks: {}, | ||
// prevent bind twice, set to true when event bound | ||
hasBindTouchEvents: false, | ||
// default options, would be override by v-touch-options | ||
options: globalOptions, | ||
events: {}, | ||
}; | ||
if (extraOptions) { | ||
touchObj.options = Object.assign({}, touchObj.options, extraOptions); | ||
} | ||
$el.$$touchObj = touchObj; | ||
return $el.$$touchObj; | ||
} | ||
} | ||
} | ||
function addTouchClass($el) { | ||
var className = $el.$$touchObj.options.touchClass; | ||
className && $el.classList.add(className); | ||
} | ||
function removeTouchClass($el) { | ||
var className = $el.$$touchObj.options.touchClass; | ||
className && $el.classList.remove(className); | ||
} | ||
function cancelTouchHoldTimer($this) { | ||
if ($this && $this.touchHoldTimer) { | ||
clearTimeout($this.touchHoldTimer); | ||
$this.touchHoldTimer = null; | ||
} | ||
} | ||
function buildTouchObj($el, extraOptions) { | ||
var touchObj = $el.$$touchObj || { | ||
element: $el, | ||
callbacks: {}, | ||
hasBindTouchEvents: false, | ||
options: globalOptions, | ||
events: {} | ||
}; | ||
if (extraOptions) { | ||
touchObj.options = Object.assign({}, touchObj.options, extraOptions); | ||
} | ||
$el.$$touchObj = touchObj; | ||
return $el.$$touchObj; | ||
} | ||
function addEvents(events) { | ||
for (const eventName in events) { | ||
if (events.hasOwnProperty(eventName)) { | ||
const [target, handler] = events[eventName]; | ||
target.addEventListener(eventName, handler); | ||
function addEvents(events) { | ||
for (const eventName in events) { | ||
if (events.hasOwnProperty(eventName)) { | ||
const [target, handler] = events[eventName]; | ||
target.addEventListener(eventName, handler); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
function removeEvents(events) { | ||
for (const eventName in events) { | ||
if (events.hasOwnProperty(eventName)) { | ||
const [target, handler] = events[eventName]; | ||
target.removeEventListener(eventName, handler); | ||
function removeEvents(events) { | ||
for (const eventName in events) { | ||
if (events.hasOwnProperty(eventName)) { | ||
const [target, handler] = events[eventName]; | ||
target.removeEventListener(eventName, handler); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
app.directive(globalOptions.namespace, { | ||
beforeMount: function($el, binding) { | ||
var $this = buildTouchObj($el); | ||
var passiveOpt = isPassiveSupported ? { passive: true } : false; | ||
var eventType = binding.arg || "tap"; | ||
switch (eventType) { | ||
case "swipe": | ||
var _m = binding.modifiers; | ||
if (_m.left || _m.right || _m.top || _m.bottom) { | ||
for (var i in binding.modifiers) { | ||
if (["left", "right", "top", "bottom"].indexOf(i) >= 0) { | ||
var _e = "swipe." + i; | ||
$this.callbacks[_e] = $this.callbacks[_e] || []; | ||
$this.callbacks[_e].push(binding); | ||
app.directive(globalOptions.namespace, { | ||
beforeMount: function ($el, binding) { | ||
// build a touch configuration object | ||
var $this = buildTouchObj($el); | ||
// declare passive option for the event listener. Defaults to { passive: true } if supported | ||
var passiveOpt = isPassiveSupported ? { passive: true } : false; | ||
// register callback | ||
var eventType = binding.arg || 'tap'; | ||
switch (eventType) { | ||
case 'swipe': | ||
var _m = binding.modifiers; | ||
if (_m.left || _m.right || _m.top || _m.bottom) { | ||
for (var i in binding.modifiers) { | ||
if (['left', 'right', 'top', 'bottom'].indexOf(i) >= 0) { | ||
var _e = 'swipe.' + i; | ||
$this.callbacks[_e] = $this.callbacks[_e] || []; | ||
$this.callbacks[_e].push(binding); | ||
} | ||
} | ||
} | ||
else { | ||
$this.callbacks.swipe = $this.callbacks.swipe || []; | ||
$this.callbacks.swipe.push(binding); | ||
} | ||
break; | ||
case 'press': | ||
case 'drag': | ||
if (binding.modifiers.disablePassive) { | ||
// change the passive option for the `drag` event if disablePassive modifier exists | ||
passiveOpt = false; | ||
} | ||
default: | ||
$this.callbacks[eventType] = $this.callbacks[eventType] || []; | ||
$this.callbacks[eventType].push(binding); | ||
} | ||
} | ||
} else { | ||
$this.callbacks.swipe = $this.callbacks.swipe || []; | ||
$this.callbacks.swipe.push(binding); | ||
// prevent bind twice | ||
if ($this.hasBindTouchEvents) { | ||
return; | ||
} | ||
// ADD MOBILE EVENTS | ||
if ($this.options.dragOutside) { | ||
$this.events['touchstart'] = [$el, touchStartEvent]; | ||
$this.events['touchmove'] = [window, touchMoveEventWindow.bind($el)]; | ||
$this.events['touchcancel'] = [window, touchCancelEvent.bind($el)]; | ||
$this.events['touchend'] = [window, touchEndEvent.bind($el)]; | ||
} | ||
else { | ||
$this.events['touchstart'] = [$el, touchStartEvent]; | ||
$this.events['touchmove'] = [$el, touchMoveEventWindow]; | ||
$this.events['touchcancel'] = [$el, touchCancelEvent]; | ||
$this.events['touchend'] = [$el, touchEndEvent.bind]; | ||
} | ||
// ADD DESKTOP EVENTS | ||
if (!$this.options.disableClick) { | ||
if ($this.options.dragOutside) { | ||
$this.events['mousedown'] = [$el, touchStartEvent]; | ||
$this.events['mousemove'] = [window, touchMoveEventWindow.bind($el)]; | ||
$this.events['mouseup'] = [window, touchEndEvent.bind($el)]; | ||
$this.events['mouseenter'] = [$el, mouseEnterEvent]; | ||
$this.events['mouseleave'] = [$el, mouseLeaveEvent]; | ||
} | ||
else { | ||
$this.events['mousedown'] = [$el, touchStartEvent]; | ||
$this.events['mousemove'] = [$el, touchMoveEvent]; | ||
$this.events['mouseup'] = [$el, touchEndEvent]; | ||
$this.events['mouseenter'] = [$el, mouseEnterEvent]; | ||
$this.events['mouseleave'] = [$el, mouseLeaveEvent]; | ||
} | ||
} | ||
// register all events | ||
addEvents($this.events); | ||
// set bind mark to true | ||
$this.hasBindTouchEvents = true; | ||
}, | ||
unmounted: function ($el) { | ||
var touchObj = $el.$$touchObj; | ||
cancelTouchHoldTimer(touchObj); | ||
// unregister all events | ||
if (touchObj && touchObj.events) { | ||
removeEvents(touchObj.events); | ||
touchObj.events = {}; | ||
} | ||
// remove vars | ||
delete $el.$$touchObj; | ||
} | ||
break; | ||
case "press": | ||
case "drag": | ||
if (binding.modifiers.disablePassive) { | ||
passiveOpt = false; | ||
}); | ||
// Register additional directives for class | ||
app.directive(`${globalOptions.namespace}-class`, { | ||
beforeMount: function ($el, binding) { | ||
buildTouchObj($el, { | ||
touchClass: binding.value | ||
}); | ||
} | ||
default: | ||
$this.callbacks[eventType] = $this.callbacks[eventType] || []; | ||
$this.callbacks[eventType].push(binding); | ||
} | ||
if ($this.hasBindTouchEvents) { | ||
return; | ||
} | ||
if ($this.options.dragOutside) { | ||
$this.events["touchstart"] = [$el, touchStartEvent]; | ||
$this.events["touchmove"] = [window, touchMoveEventWindow.bind($el)]; | ||
$this.events["touchcancel"] = [window, touchCancelEvent.bind($el)]; | ||
$this.events["touchend"] = [window, touchEndEvent.bind($el)]; | ||
} else { | ||
$this.events["touchstart"] = [$el, touchStartEvent]; | ||
$this.events["touchmove"] = [$el, touchMoveEventWindow]; | ||
$this.events["touchcancel"] = [$el, touchCancelEvent]; | ||
$this.events["touchend"] = [$el, touchEndEvent.bind]; | ||
} | ||
if (!$this.options.disableClick) { | ||
if ($this.options.dragOutside) { | ||
$this.events["mousedown"] = [$el, touchStartEvent]; | ||
$this.events["mousemove"] = [window, touchMoveEventWindow.bind($el)]; | ||
$this.events["mouseup"] = [window, touchEndEvent.bind($el)]; | ||
$this.events["mouseenter"] = [$el, mouseEnterEvent]; | ||
$this.events["mouseleave"] = [$el, mouseLeaveEvent]; | ||
} else { | ||
$this.events["mousedown"] = [$el, touchStartEvent]; | ||
$this.events["mousemove"] = [$el, touchMoveEvent]; | ||
$this.events["mouseup"] = [$el, touchEndEvent]; | ||
$this.events["mouseenter"] = [$el, mouseEnterEvent]; | ||
$this.events["mouseleave"] = [$el, mouseLeaveEvent]; | ||
} | ||
} | ||
addEvents($this.events); | ||
$this.hasBindTouchEvents = true; | ||
}, | ||
unmounted: function($el) { | ||
var touchObj = $el.$$touchObj; | ||
cancelTouchHoldTimer(touchObj); | ||
if (touchObj && touchObj.events) { | ||
removeEvents(touchObj.events); | ||
touchObj.events = {}; | ||
} | ||
delete $el.$$touchObj; | ||
} | ||
}); | ||
app.directive(`${globalOptions.namespace}-class`, { | ||
beforeMount: function($el, binding) { | ||
buildTouchObj($el, { | ||
touchClass: binding.value | ||
}); | ||
} | ||
}); | ||
app.directive(`${globalOptions.namespace}-options`, { | ||
beforeMount: function($el, binding) { | ||
buildTouchObj($el, binding.value); | ||
} | ||
}); | ||
} | ||
// Register additional directives for options | ||
app.directive(`${globalOptions.namespace}-options`, { | ||
beforeMount: function ($el, binding) { | ||
buildTouchObj($el, binding.value); | ||
} | ||
}); | ||
} | ||
}; | ||
var vue3_touch_events_default = vueTouchEvents; | ||
export { | ||
vue3_touch_events_default as default | ||
}; | ||
/* | ||
* Exports | ||
*/ | ||
export default Vue3TouchEvents; |
{ | ||
"name": "vue3-touch-events", | ||
"version": "5.0.10", | ||
"version": "5.0.11", | ||
"description": "Simple touch events support for vue.js 3", | ||
@@ -8,3 +8,3 @@ "type": "module", | ||
"module": "dist/index.js", | ||
"types": "index.d.ts", | ||
"types": "dist/index.d.ts", | ||
"files": [ | ||
@@ -14,4 +14,4 @@ "dist" | ||
"scripts": { | ||
"build": "bun build ./index.js --outdir ./dist", | ||
"dev": "bun build ./index.js --outdir ./dist --watch", | ||
"build": "bun build ./src/index.ts --outdir ./dist && bunx tsc", | ||
"dev": "bun build ./src/index.ts --outdir ./dist --watch & bunx tsc --watch", | ||
"test": "bun test" | ||
@@ -30,3 +30,3 @@ }, | ||
], | ||
"author": "Robin Rodricks, Xavier Julien, Jerry Bendy", | ||
"author": "Robin Rodricks", | ||
"license": "MIT", | ||
@@ -38,3 +38,4 @@ "bugs": { | ||
"devDependencies": { | ||
"bun-types": "latest" | ||
"bun-types": "latest", | ||
"typescript": "^5.7.3" | ||
}, | ||
@@ -41,0 +42,0 @@ "peerDependencies": { |
@@ -12,2 +12,8 @@ # vue3-touch-events [![](https://img.shields.io/npm/v/vue3-touch-events.svg)](https://www.npmjs.com/package/vue3-touch-events) [![](https://img.shields.io/npm/dt/vue3-touch-events)](https://www.npmjs.com/package/vue3-touch-events) | ||
## What's New! | ||
- [Cone swiping](#swipe) and [Zoom](#zoom) is new in version 5! | ||
- We now support sending [drag](#drag-and-drop) events when users drag outside an object! (set `dragOutside: true`) | ||
- If you encounter any issues with these, please file an issue! | ||
## Features | ||
@@ -42,2 +48,5 @@ | ||
Release notes are found in the [RELEASES.md](RELEASES.md) file. | ||
### TypeScript | ||
@@ -70,3 +79,3 @@ | ||
Global options that can be added are [listed here](#global-configuration). | ||
Global options that can be added are [listed here](#global-configuration) and listed in each [feature section](#touch-and-tap). | ||
@@ -87,6 +96,3 @@ | ||
```ts | ||
<div | ||
v-touch:swipe.left="() => swipe('next')" | ||
v-touch:swipe.right="() => swipe('prev')" | ||
> | ||
<div v-touch:swipe.left="() => swipe('next')" v-touch:swipe.right="() => swipe('prev')"> | ||
``` | ||
@@ -213,3 +219,3 @@ | ||
These settings can be optionally specified in the [Global Config](#global-configuration). If they are not specified, defaults are used. | ||
These settings can be optionally specified in the [Global Config](#global-configuration) or [Object Config](#v-touch-options). If they are not specified, defaults are used. | ||
@@ -262,3 +268,3 @@ | Setting | Units | Comment | | ||
These settings can be optionally specified in the [Global Config](#global-configuration). If they are not specified, defaults are used. | ||
These settings can be optionally specified in the [Global Config](#global-configuration) or [Object Config](#v-touch-options). If they are not specified, defaults are used. | ||
@@ -280,11 +286,11 @@ | Setting | Units | Comment | | ||
| ----------- | -------- | ------------------ | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/events0005.png) | `v-touch:swipe` | Triggered when the user drags on the element (swipe). <br> It will detect the direction of the swipe and send it to your callback. <br> First argument of the callback must be `direction` attribute, which can be `left`, `right`, `top` or `bottom`. <br> Example callback: `onSwipe(direction){ ... }` | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/events0004.png) | `v-touch:swipe.left` | Triggered when the user drags on the element within the left cone. | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/events0003.png) | `v-touch:swipe.right` | Triggered when the user drags on the element within the right cone. | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/events0001.png) | `v-touch:swipe.top` | Triggered when the user drags on the element within the top cone. | | ||
|![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/events0002.png) | `v-touch:swipe.bottom` | Triggered when the user drags on the element within the bottom cone. | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0005.png) | `v-touch:swipe` | Triggered when the user drags on the element (swipe). <br> It will detect the direction of the swipe and send it to your callback. <br> First argument of the callback must be `direction` attribute, which can be `left`, `right`, `top` or `bottom`. <br> Example callback: `onSwipe(direction){ ... }` | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0004.png) | `v-touch:swipe.left` | Triggered when the user drags on the element within the left cone. | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0003.png) | `v-touch:swipe.right` | Triggered when the user drags on the element within the right cone. | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0001.png) | `v-touch:swipe.top` | Triggered when the user drags on the element within the top cone. | | ||
|![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0002.png) | `v-touch:swipe.bottom` | Triggered when the user drags on the element within the bottom cone. | | ||
### Swipe Settings | ||
These settings can be optionally specified in the [Global Config](#global-configuration). If they are not specified, defaults are used. | ||
These settings can be optionally specified in the [Global Config](#global-configuration) or [Object Config](#v-touch-options). If they are not specified, defaults are used. | ||
@@ -309,9 +315,9 @@ | Setting | Units | Comment | | ||
| ----------- | -------- | ------------------ | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/events0008.png) | `v-touch:zoom` | **Mobile only:** Triggered when the user presses 2 fingers down and moves them inward or outward. This event is continuously fired as the user is zooming. <br> First argument of the callback will recieve the zoom factor. <br> Example callback: `onZoom(zoomFactor){ ... }` | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/events0006.png) | `v-touch:zoom.in` | **Mobile only:** Triggered when the user presses 2 fingers down and moves them towards each other (the normal "zoom in" gesture) | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/events0007.png) | `v-touch:zoom.out` | **Mobile only:** Triggered when the user presses 2 fingers down and moves them away from each other (the normal "zoom out" gesture) | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0008.png) | `v-touch:zoom` | **Mobile only:** Triggered when the user presses 2 fingers down and moves them inward or outward. This event is continuously fired as the user is zooming. <br> First argument of the callback will recieve the zoom factor. <br> Example callback: `onZoom(zoomFactor){ ... }` | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0006.png) | `v-touch:zoom.in` | **Mobile only:** Triggered when the user presses 2 fingers down and moves them towards each other (the normal "zoom in" gesture) | | ||
| ![Pic](https://github.com/robinrodricks/vue3-touch-events/raw/master/images/gestures0007.png) | `v-touch:zoom.out` | **Mobile only:** Triggered when the user presses 2 fingers down and moves them away from each other (the normal "zoom out" gesture) | | ||
### Zoom Settings | ||
These settings can be optionally specified in the [Global Config](#global-configuration). If they are not specified, defaults are used. | ||
These settings can be optionally specified in the [Global Config](#global-configuration) or [Object Config](#v-touch-options). If they are not specified, defaults are used. | ||
@@ -433,5 +439,15 @@ | Setting | Units | Comment | | ||
## Building from Source | ||
1. First install `bun` on your system. Node is not required. | ||
2. Run these commands: | ||
``` | ||
bun install | ||
bun run build | ||
``` | ||
## Credits | ||
- All credits go to Jerry Bendy for creating the original project [vue2-touch-events](https://github.com/jerrybendy/vue-touch-events) | ||
- Credits go to Jerry Bendy for creating the original project [vue2-touch-events](https://github.com/jerrybendy/vue-touch-events) | ||
- Special thanks to Xavier Julien for creating the [Vue 3.x port](https://github.com/XjulI1/vue-touch-events/tree/migrate-to-vuejs-3) |
49670
5
536
445
2