Skip to content

Commit 7346a6a

Browse files
noookantfu
andauthoredMay 27, 2024··
feat(onLongPress): options.onMouseUp callback (#3791)
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
1 parent 4636f4c commit 7346a6a

File tree

3 files changed

+106
-3
lines changed

3 files changed

+106
-3
lines changed
 

‎packages/core/onLongPress/demo.vue

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,55 @@
11
<script setup lang="ts">
22
import { ref } from 'vue'
3-
import { onLongPress } from '@vueuse/core'
3+
import { onLongPress } from './'
44
55
const htmlRef = ref<HTMLElement | null>(null)
66
const htmlRefOptions = ref<HTMLElement | null>(null)
7+
const htmlRefOnMouseUp = ref<HTMLElement | null>(null)
78
89
const longPressed = ref(false)
10+
const clicked = ref(false)
911
1012
function onLongPressCallback(e: PointerEvent) {
1113
longPressed.value = true
1214
}
1315
16+
function onMouseUpCallback(duration: number, distance: number, isLongPress: boolean) {
17+
if (!isLongPress)
18+
clicked.value = true
19+
20+
console.log({ distance, duration, isLongPress })
21+
}
22+
1423
function reset() {
1524
longPressed.value = false
25+
clicked.value = false
1626
}
1727
1828
onLongPress(htmlRef, onLongPressCallback)
1929
onLongPress(htmlRefOptions, onLongPressCallback, { delay: 1000 })
30+
onLongPress(
31+
htmlRefOnMouseUp,
32+
onLongPressCallback,
33+
{
34+
distanceThreshold: 24,
35+
delay: 1000,
36+
onMouseUp: onMouseUpCallback,
37+
},
38+
)
2039
</script>
2140

2241
<template>
2342
<p>Long Pressed: <BooleanDisplay :value="longPressed" /></p>
43+
<p>Clicked: <BooleanDisplay :value="clicked" /></p>
2444
<button ref="htmlRef" class="ml-2 button small">
2545
Press long (500ms)
2646
</button>
2747
<button ref="htmlRefOptions" class="ml-2 button small">
2848
Press long (1000ms)
2949
</button>
50+
<button ref="htmlRefOnMouseUp" class="ml-2 button small">
51+
Press long (1000ms) or click
52+
</button>
3053
<button class="ml-2 button small" @click="reset">
3154
Reset
3255
</button>

‎packages/core/onLongPress/index.test.ts

+42
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,47 @@ describe('onLongPress', () => {
142142
expect(onLongPressCallback).toHaveBeenCalledTimes(1)
143143
}
144144

145+
async function triggerOnMouseUp(isRef: boolean) {
146+
const onLongPressCallback = vi.fn()
147+
const onMouseUpCallback = vi.fn()
148+
onLongPress(isRef ? element : element.value, onLongPressCallback, { onMouseUp: onMouseUpCallback })
149+
150+
// first pointer down
151+
pointerdownEvent = new PointerEvent('pointerdown', { cancelable: true, bubbles: true })
152+
element.value.dispatchEvent(pointerdownEvent)
153+
154+
// wait for 250 after pointer down
155+
await promiseTimeout(250)
156+
expect(onLongPressCallback).toHaveBeenCalledTimes(0)
157+
expect(onMouseUpCallback).toHaveBeenCalledTimes(0)
158+
159+
// pointer up to cancel callback
160+
pointerUpEvent = new PointerEvent('pointerup', { cancelable: true, bubbles: true })
161+
element.value.dispatchEvent(pointerUpEvent)
162+
expect(onMouseUpCallback).toHaveBeenCalledTimes(1)
163+
expect(onMouseUpCallback).toBeCalledWith(expect.any(Number), 0, false)
164+
expect(onMouseUpCallback.mock.calls[0][0]).toBeGreaterThanOrEqual(250)
165+
166+
// wait for 500ms after pointer up
167+
await promiseTimeout(500)
168+
expect(onLongPressCallback).toHaveBeenCalledTimes(0)
169+
170+
// another pointer down
171+
pointerdownEvent = new PointerEvent('pointerdown', { cancelable: true, bubbles: true })
172+
element.value.dispatchEvent(pointerdownEvent)
173+
174+
// wait for 500 after pointer down
175+
await promiseTimeout(500)
176+
expect(onLongPressCallback).toHaveBeenCalledTimes(1)
177+
expect(onMouseUpCallback).toHaveBeenCalledTimes(1)
178+
179+
pointerUpEvent = new PointerEvent('pointerup', { cancelable: true, bubbles: true })
180+
element.value.dispatchEvent(pointerUpEvent)
181+
expect(onMouseUpCallback).toHaveBeenCalledTimes(2)
182+
expect(onMouseUpCallback).toBeCalledWith(expect.any(Number), 0, true)
183+
expect(onMouseUpCallback.mock.calls[1][0]).toBeGreaterThanOrEqual(500)
184+
}
185+
145186
function suites(isRef: boolean) {
146187
describe('given no options', () => {
147188
it('should trigger longpress after 500ms', () => triggerCallback(isRef))
@@ -154,6 +195,7 @@ describe('onLongPress', () => {
154195
it('should stop propagation', () => stopPropagation(isRef))
155196
it('should remove event listeners after being stopped', () => stopEventListeners(isRef))
156197
it('should trigger longpress if pointer is moved', () => triggerCallbackWithThreshold(isRef))
198+
it('should trigger onMouseUp when pointer is released', () => triggerOnMouseUp(isRef))
157199
})
158200
}
159201

‎packages/core/onLongPress/index.ts

+40-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ export interface OnLongPressOptions {
2323
* @default 10
2424
*/
2525
distanceThreshold?: number | false
26+
27+
/**
28+
* Function called when the ref element is released.
29+
* @param duration how long the element was pressed in ms
30+
* @param distance distance from the pointerdown position
31+
* @param isLongPress whether the action was a long press or not
32+
*/
33+
onMouseUp?: (duration: number, distance: number, isLongPress: boolean) => void
2634
}
2735

2836
export interface OnLongPressModifiers {
@@ -42,13 +50,39 @@ export function onLongPress(
4250

4351
let timeout: ReturnType<typeof setTimeout> | undefined
4452
let posStart: Position | undefined
53+
let startTimestamp: number | undefined
54+
let hasLongPressed = false
4555

4656
function clear() {
4757
if (timeout) {
4858
clearTimeout(timeout)
4959
timeout = undefined
5060
}
5161
posStart = undefined
62+
startTimestamp = undefined
63+
hasLongPressed = false
64+
}
65+
66+
function onRelease(ev: PointerEvent) {
67+
const [_startTimestamp, _posStart, _hasLongPressed] = [startTimestamp, posStart, hasLongPressed]
68+
clear()
69+
70+
if (!options?.onMouseUp || !_posStart || !_startTimestamp)
71+
return
72+
73+
if (options?.modifiers?.self && ev.target !== elementRef.value)
74+
return
75+
76+
if (options?.modifiers?.prevent)
77+
ev.preventDefault()
78+
79+
if (options?.modifiers?.stop)
80+
ev.stopPropagation()
81+
82+
const dx = ev.x - _posStart.x
83+
const dy = ev.y - _posStart.y
84+
const distance = Math.sqrt(dx * dx + dy * dy)
85+
options.onMouseUp(ev.timeStamp - _startTimestamp, distance, _hasLongPressed)
5286
}
5387

5488
function onDown(ev: PointerEvent) {
@@ -67,8 +101,12 @@ export function onLongPress(
67101
x: ev.x,
68102
y: ev.y,
69103
}
104+
startTimestamp = ev.timeStamp
70105
timeout = setTimeout(
71-
() => handler(ev),
106+
() => {
107+
hasLongPressed = true
108+
handler(ev)
109+
},
72110
options?.delay ?? DEFAULT_DELAY,
73111
)
74112
}
@@ -101,7 +139,7 @@ export function onLongPress(
101139
const cleanup = [
102140
useEventListener(elementRef, 'pointerdown', onDown, listenerOptions),
103141
useEventListener(elementRef, 'pointermove', onMove, listenerOptions),
104-
useEventListener(elementRef, ['pointerup', 'pointerleave'], clear, listenerOptions),
142+
useEventListener(elementRef, ['pointerup', 'pointerleave'], onRelease, listenerOptions),
105143
]
106144

107145
const stop = () => cleanup.forEach(fn => fn())

0 commit comments

Comments
 (0)
Please sign in to comment.