Skip to content

Commit 7985915

Browse files
committed
fix: drag-reorder on mobile
1 parent d48dbba commit 7985915

1 file changed

Lines changed: 41 additions & 13 deletions

File tree

src/composables/useTouchReorder.ts

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { onBeforeUnmount, onMounted, ref, type Ref } from 'vue'
1+
import { onBeforeUnmount, onMounted, ref, watch, type Ref } from 'vue'
22

33
export interface TouchReorderCallbacks {
44
onDragStart: (_id: number) => void
@@ -7,7 +7,7 @@ export interface TouchReorderCallbacks {
77
onCancel: () => void
88
}
99

10-
const LONG_PRESS_MS = 300
10+
const LONG_PRESS_MS = 400
1111
const MOVE_THRESHOLD = 8
1212
const SCROLL_EDGE = 40
1313
const SCROLL_SPEED = 8
@@ -120,6 +120,13 @@ export function useTouchReorder(
120120
dragId = null
121121
}
122122

123+
// Suppress the browser context menu while a long-press drag is pending or active.
124+
function onContextMenu(e: Event) {
125+
if (longPressTimer || isTouchDragging.value) {
126+
e.preventDefault()
127+
}
128+
}
129+
123130
function onTouchStart(e: TouchEvent) {
124131
if (enabled && !enabled.value) return
125132
const touch = e.touches[0]
@@ -161,8 +168,9 @@ export function useTouchReorder(
161168
return
162169
}
163170

164-
// We're dragging — prevent scroll
171+
// We're dragging — prevent scroll and default behaviour
165172
e.preventDefault()
173+
e.stopPropagation()
166174

167175
moveGhost(touch.clientX, touch.clientY)
168176
autoScroll(touch.clientY)
@@ -176,35 +184,55 @@ export function useTouchReorder(
176184
}
177185
}
178186

179-
function onTouchEnd() {
187+
function onTouchEnd(e: TouchEvent) {
180188
clearLongPress()
181189
stopAutoScroll()
182190
removeGhost()
183191

184192
if (isTouchDragging.value) {
193+
// Prevent the browser from synthesising a click / mousedown after the drag.
194+
e.preventDefault()
185195
isTouchDragging.value = false
186196
callbacks.onDrop()
187197
}
188198
dragId = null
189199
}
190200

191-
onMounted(() => {
192-
const el = containerRef.value
193-
if (!el) return
201+
function bind(el: HTMLElement) {
194202
el.addEventListener('touchstart', onTouchStart, { passive: false })
195203
el.addEventListener('touchmove', onTouchMove, { passive: false })
196-
el.addEventListener('touchend', onTouchEnd)
204+
el.addEventListener('touchend', onTouchEnd, { passive: false })
197205
el.addEventListener('touchcancel', cancelDrag)
198-
})
206+
el.addEventListener('contextmenu', onContextMenu)
207+
}
199208

200-
onBeforeUnmount(() => {
201-
cancelDrag()
202-
const el = containerRef.value
203-
if (!el) return
209+
function unbind(el: HTMLElement) {
204210
el.removeEventListener('touchstart', onTouchStart)
205211
el.removeEventListener('touchmove', onTouchMove)
206212
el.removeEventListener('touchend', onTouchEnd)
207213
el.removeEventListener('touchcancel', cancelDrag)
214+
el.removeEventListener('contextmenu', onContextMenu)
215+
}
216+
217+
// Re-bind when the container ref changes (e.g. v-if toggling the element).
218+
let boundEl: HTMLElement | null = null
219+
function syncBinding() {
220+
const el = containerRef.value
221+
if (el === boundEl) return
222+
if (boundEl) unbind(boundEl)
223+
boundEl = el
224+
if (el) bind(el)
225+
}
226+
227+
onMounted(syncBinding)
228+
watch(containerRef, syncBinding)
229+
230+
onBeforeUnmount(() => {
231+
cancelDrag()
232+
if (boundEl) {
233+
unbind(boundEl)
234+
boundEl = null
235+
}
208236
})
209237

210238
return { isTouchDragging }

0 commit comments

Comments
 (0)