diff --git a/app/javascript/controllers/swipe_nav_controller.js b/app/javascript/controllers/swipe_nav_controller.js index 8f0272c..c35953f 100644 --- a/app/javascript/controllers/swipe_nav_controller.js +++ b/app/javascript/controllers/swipe_nav_controller.js @@ -9,12 +9,18 @@ export default class extends Controller { this.onPointerDown = this.onPointerDown.bind(this) this.onPointerMove = this.onPointerMove.bind(this) this.onPointerUp = this.onPointerUp.bind(this) + this.onTouchStart = this.onTouchStart.bind(this) + this.onTouchMove = this.onTouchMove.bind(this) + this.onTouchEnd = this.onTouchEnd.bind(this) window.addEventListener("pointerdown", this.onPointerDown, { passive: true }) + window.addEventListener("touchstart", this.onTouchStart, { passive: true }) } disconnect() { window.removeEventListener("pointerdown", this.onPointerDown, { passive: true }) + window.removeEventListener("touchstart", this.onTouchStart, { passive: true }) this.detachTrackingListeners() + this.detachTouchTrackingListeners() } onPointerDown(event) { @@ -23,6 +29,7 @@ export default class extends Controller { if (event.button && event.button !== 0) return if (this.shouldIgnoreTarget(event.target)) return + this.usingPointer = true this.tracking = true this.pointerId = event.pointerId this.startX = event.clientX @@ -51,8 +58,63 @@ export default class extends Controller { this.tracking = false this.pointerId = null + this.usingPointer = false this.detachTrackingListeners() + this.maybeNavigate(elapsed, deltaX, deltaY) + } + + detachTrackingListeners() { + window.removeEventListener("pointermove", this.onPointerMove, { passive: true }) + window.removeEventListener("pointerup", this.onPointerUp, { passive: true }) + window.removeEventListener("pointercancel", this.onPointerUp, { passive: true }) + } + + onTouchStart(event) { + if (this.usingPointer) return + if (!event.touches || event.touches.length !== 1) return + if (this.shouldIgnoreTarget(event.target)) return + + const touch = event.touches[0] + this.touchTracking = true + this.startX = touch.clientX + this.startY = touch.clientY + this.startTime = performance.now() + this.lastX = touch.clientX + this.lastY = touch.clientY + + window.addEventListener("touchmove", this.onTouchMove, { passive: true }) + window.addEventListener("touchend", this.onTouchEnd, { passive: true }) + window.addEventListener("touchcancel", this.onTouchEnd, { passive: true }) + } + + onTouchMove(event) { + if (!this.touchTracking || !event.touches || event.touches.length !== 1) return + const touch = event.touches[0] + this.lastX = touch.clientX + this.lastY = touch.clientY + } + + onTouchEnd() { + if (!this.touchTracking) return + + const elapsed = performance.now() - this.startTime + const deltaX = this.lastX - this.startX + const deltaY = this.lastY - this.startY + + this.touchTracking = false + this.detachTouchTrackingListeners() + + this.maybeNavigate(elapsed, deltaX, deltaY) + } + + detachTouchTrackingListeners() { + window.removeEventListener("touchmove", this.onTouchMove, { passive: true }) + window.removeEventListener("touchend", this.onTouchEnd, { passive: true }) + window.removeEventListener("touchcancel", this.onTouchEnd, { passive: true }) + } + + maybeNavigate(elapsed, deltaX, deltaY) { if (elapsed > SWIPE_TIME_MS) return if (Math.abs(deltaX) < SWIPE_DISTANCE_PX) return if (Math.abs(deltaX) < Math.abs(deltaY) * SWIPE_AXIS_RATIO) return @@ -64,12 +126,6 @@ export default class extends Controller { } } - detachTrackingListeners() { - window.removeEventListener("pointermove", this.onPointerMove, { passive: true }) - window.removeEventListener("pointerup", this.onPointerUp, { passive: true }) - window.removeEventListener("pointercancel", this.onPointerUp, { passive: true }) - } - shouldIgnoreTarget(target) { if (!target) return true if (target.closest("input, textarea, select, [contenteditable='true']")) return true