Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "Fix: draggable touch delay (#3616)" #3654

Merged
merged 1 commit into from
Apr 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 73 additions & 77 deletions src/draggable.ts
Original file line number Diff line number Diff line change
@@ -1,115 +1,111 @@
export function makeDraggable(
element: HTMLElement,
element: HTMLElement | null,
onDrag: (dx: number, dy: number, x: number, y: number) => void,
onStart?: (x: number, y: number) => void,
onEnd?: (x: number, y: number) => void,
threshold = 3,
mouseButton = 0,
touchDelay = 100,
) {
): () => void {
if (!element) return () => void 0

let isPointerDown = false
let isDragging = false
let startX = 0
let startY = 0
let touchStart = 0
let rect: DOMRect
const isTouchDevice = matchMedia('(pointer: coarse)').matches

const onPointerDown = (e: PointerEvent) => {
if (e.button !== mouseButton) return
e.stopPropagation()
let unsubscribeDocument = () => void 0

isPointerDown = true
isDragging = false
touchStart = Date.now()
rect = element.getBoundingClientRect()
startX = e.clientX - rect.left
startY = e.clientY - rect.top
}
const onPointerDown = (event: PointerEvent) => {
if (event.button !== mouseButton) return

const onPointerUp = (e: PointerEvent) => {
isPointerDown = false
touchStart = 0
event.preventDefault()
event.stopPropagation()

if (isDragging) {
e.preventDefault()
e.stopPropagation()
let startX = event.clientX
let startY = event.clientY
let isDragging = false
const touchStartTime = Date.now()

setTimeout(() => {
isDragging = false
}, 300)
const onPointerMove = (event: PointerEvent) => {
event.preventDefault()
event.stopPropagation()

const x = e.clientX - rect.left
const y = e.clientY - rect.top
onEnd?.(x, y)
}
}
if (isTouchDevice && Date.now() - touchStartTime < touchDelay) return

const onPointerMove = (e: PointerEvent) => {
if (!isPointerDown) return
if (isTouchDevice && Date.now() - touchStart < touchDelay) return
const x = event.clientX
const y = event.clientY
const dx = x - startX
const dy = y - startY

e.preventDefault()
e.stopPropagation()
if (isDragging || Math.abs(dx) > threshold || Math.abs(dy) > threshold) {
const rect = element.getBoundingClientRect()
const { left, top } = rect

const x = e.clientX - rect.left
const y = e.clientY - rect.top
const dx = x - startX
const dy = y - startY
if (!isDragging) {
onStart?.(startX - left, startY - top)
isDragging = true
}

if (isDragging || Math.abs(dx) > threshold || Math.abs(dy) > threshold) {
if (!isDragging) {
onStart?.(startX, startY)
isDragging = true
onDrag(dx, dy, x - left, y - top)

startX = x
startY = y
}
}

onDrag(dx, dy, x, y)
const onPointerUp = (event: PointerEvent) => {
if (isDragging) {
const x = event.clientX
const y = event.clientY
const rect = element.getBoundingClientRect()
const { left, top } = rect

startX = x
startY = y
onEnd?.(x - left, y - top)
}
unsubscribeDocument()
}
}

const onPointerLeave = (e: PointerEvent) => {
// Listen to events only on the document and not on inner elements
if (!e.relatedTarget || e.relatedTarget === document.documentElement) {
onPointerUp(e)
const onPointerLeave = (e: PointerEvent) => {
// Listen to events only on the document and not on inner elements
if (!e.relatedTarget || e.relatedTarget === document.documentElement) {
onPointerUp(e)
}
}
}

const onTouchMove = (e: TouchEvent) => {
if (isDragging) {
e.preventDefault()
const onClick = (event: MouseEvent) => {
if (isDragging) {
event.stopPropagation()
event.preventDefault()
}
}
}

// Prevent clicks after dragging
const onClick = (e: MouseEvent) => {
if (isDragging) {
e.stopPropagation()
e.preventDefault()
const onTouchMove = (event: TouchEvent) => {
if (isDragging) {
event.preventDefault()
}
}

document.addEventListener('pointermove', onPointerMove)
document.addEventListener('pointerup', onPointerUp)
document.addEventListener('pointerout', onPointerLeave)
document.addEventListener('pointercancel', onPointerLeave)
document.addEventListener('touchmove', onTouchMove, { passive: false })
document.addEventListener('click', onClick, { capture: true })

unsubscribeDocument = () => {
document.removeEventListener('pointermove', onPointerMove)
document.removeEventListener('pointerup', onPointerUp)
document.removeEventListener('pointerout', onPointerLeave)
document.removeEventListener('pointercancel', onPointerLeave)
document.removeEventListener('touchmove', onTouchMove)
setTimeout(() => {
document.removeEventListener('click', onClick, { capture: true })
}, 10)
}
}

element.addEventListener('pointerdown', onPointerDown)
element.addEventListener('click', onClick, true)
document.addEventListener('click', onClick, true)
document.addEventListener('pointermove', onPointerMove)
document.addEventListener('touchmove', onTouchMove, { passive: false })
document.addEventListener('pointerup', onPointerUp)
document.addEventListener('pointerleave', onPointerLeave)
document.addEventListener('pointercancel', onPointerUp)

return () => {
unsubscribeDocument()
element.removeEventListener('pointerdown', onPointerDown)
element.removeEventListener('click', onClick, true)
document.removeEventListener('click', onClick, true)
document.removeEventListener('pointermove', onPointerMove)
document.removeEventListener('touchmove', onTouchMove)
document.removeEventListener('pointerup', onPointerUp)
document.removeEventListener('pointerleave', onPointerLeave)
document.removeEventListener('pointercancel', onPointerUp)
}
}
Loading