Skip to content

Commit

Permalink
feat(onLongPress): options.onMouseUp callback (#3791)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <[email protected]>
  • Loading branch information
noook and antfu authored May 27, 2024
1 parent 4636f4c commit 7346a6a
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 3 deletions.
25 changes: 24 additions & 1 deletion packages/core/onLongPress/demo.vue
Original file line number Diff line number Diff line change
@@ -1,32 +1,55 @@
<script setup lang="ts">
import { ref } from 'vue'
import { onLongPress } from '@vueuse/core'
import { onLongPress } from './'
const htmlRef = ref<HTMLElement | null>(null)
const htmlRefOptions = ref<HTMLElement | null>(null)
const htmlRefOnMouseUp = ref<HTMLElement | null>(null)
const longPressed = ref(false)
const clicked = ref(false)
function onLongPressCallback(e: PointerEvent) {
longPressed.value = true
}
function onMouseUpCallback(duration: number, distance: number, isLongPress: boolean) {
if (!isLongPress)
clicked.value = true
console.log({ distance, duration, isLongPress })
}
function reset() {
longPressed.value = false
clicked.value = false
}
onLongPress(htmlRef, onLongPressCallback)
onLongPress(htmlRefOptions, onLongPressCallback, { delay: 1000 })
onLongPress(
htmlRefOnMouseUp,
onLongPressCallback,
{
distanceThreshold: 24,
delay: 1000,
onMouseUp: onMouseUpCallback,
},
)
</script>

<template>
<p>Long Pressed: <BooleanDisplay :value="longPressed" /></p>
<p>Clicked: <BooleanDisplay :value="clicked" /></p>
<button ref="htmlRef" class="ml-2 button small">
Press long (500ms)
</button>
<button ref="htmlRefOptions" class="ml-2 button small">
Press long (1000ms)
</button>
<button ref="htmlRefOnMouseUp" class="ml-2 button small">
Press long (1000ms) or click
</button>
<button class="ml-2 button small" @click="reset">
Reset
</button>
Expand Down
42 changes: 42 additions & 0 deletions packages/core/onLongPress/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,47 @@ describe('onLongPress', () => {
expect(onLongPressCallback).toHaveBeenCalledTimes(1)
}

async function triggerOnMouseUp(isRef: boolean) {
const onLongPressCallback = vi.fn()
const onMouseUpCallback = vi.fn()
onLongPress(isRef ? element : element.value, onLongPressCallback, { onMouseUp: onMouseUpCallback })

// first pointer down
pointerdownEvent = new PointerEvent('pointerdown', { cancelable: true, bubbles: true })
element.value.dispatchEvent(pointerdownEvent)

// wait for 250 after pointer down
await promiseTimeout(250)
expect(onLongPressCallback).toHaveBeenCalledTimes(0)
expect(onMouseUpCallback).toHaveBeenCalledTimes(0)

// pointer up to cancel callback
pointerUpEvent = new PointerEvent('pointerup', { cancelable: true, bubbles: true })
element.value.dispatchEvent(pointerUpEvent)
expect(onMouseUpCallback).toHaveBeenCalledTimes(1)
expect(onMouseUpCallback).toBeCalledWith(expect.any(Number), 0, false)
expect(onMouseUpCallback.mock.calls[0][0]).toBeGreaterThanOrEqual(250)

// wait for 500ms after pointer up
await promiseTimeout(500)
expect(onLongPressCallback).toHaveBeenCalledTimes(0)

// another pointer down
pointerdownEvent = new PointerEvent('pointerdown', { cancelable: true, bubbles: true })
element.value.dispatchEvent(pointerdownEvent)

// wait for 500 after pointer down
await promiseTimeout(500)
expect(onLongPressCallback).toHaveBeenCalledTimes(1)
expect(onMouseUpCallback).toHaveBeenCalledTimes(1)

pointerUpEvent = new PointerEvent('pointerup', { cancelable: true, bubbles: true })
element.value.dispatchEvent(pointerUpEvent)
expect(onMouseUpCallback).toHaveBeenCalledTimes(2)
expect(onMouseUpCallback).toBeCalledWith(expect.any(Number), 0, true)
expect(onMouseUpCallback.mock.calls[1][0]).toBeGreaterThanOrEqual(500)
}

function suites(isRef: boolean) {
describe('given no options', () => {
it('should trigger longpress after 500ms', () => triggerCallback(isRef))
Expand All @@ -154,6 +195,7 @@ describe('onLongPress', () => {
it('should stop propagation', () => stopPropagation(isRef))
it('should remove event listeners after being stopped', () => stopEventListeners(isRef))
it('should trigger longpress if pointer is moved', () => triggerCallbackWithThreshold(isRef))
it('should trigger onMouseUp when pointer is released', () => triggerOnMouseUp(isRef))
})
}

Expand Down
42 changes: 40 additions & 2 deletions packages/core/onLongPress/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ export interface OnLongPressOptions {
* @default 10
*/
distanceThreshold?: number | false

/**
* Function called when the ref element is released.
* @param duration how long the element was pressed in ms
* @param distance distance from the pointerdown position
* @param isLongPress whether the action was a long press or not
*/
onMouseUp?: (duration: number, distance: number, isLongPress: boolean) => void
}

export interface OnLongPressModifiers {
Expand All @@ -42,13 +50,39 @@ export function onLongPress(

let timeout: ReturnType<typeof setTimeout> | undefined
let posStart: Position | undefined
let startTimestamp: number | undefined
let hasLongPressed = false

function clear() {
if (timeout) {
clearTimeout(timeout)
timeout = undefined
}
posStart = undefined
startTimestamp = undefined
hasLongPressed = false
}

function onRelease(ev: PointerEvent) {
const [_startTimestamp, _posStart, _hasLongPressed] = [startTimestamp, posStart, hasLongPressed]
clear()

if (!options?.onMouseUp || !_posStart || !_startTimestamp)
return

if (options?.modifiers?.self && ev.target !== elementRef.value)
return

if (options?.modifiers?.prevent)
ev.preventDefault()

if (options?.modifiers?.stop)
ev.stopPropagation()

const dx = ev.x - _posStart.x
const dy = ev.y - _posStart.y
const distance = Math.sqrt(dx * dx + dy * dy)
options.onMouseUp(ev.timeStamp - _startTimestamp, distance, _hasLongPressed)
}

function onDown(ev: PointerEvent) {
Expand All @@ -67,8 +101,12 @@ export function onLongPress(
x: ev.x,
y: ev.y,
}
startTimestamp = ev.timeStamp
timeout = setTimeout(
() => handler(ev),
() => {
hasLongPressed = true
handler(ev)
},
options?.delay ?? DEFAULT_DELAY,
)
}
Expand Down Expand Up @@ -101,7 +139,7 @@ export function onLongPress(
const cleanup = [
useEventListener(elementRef, 'pointerdown', onDown, listenerOptions),
useEventListener(elementRef, 'pointermove', onMove, listenerOptions),
useEventListener(elementRef, ['pointerup', 'pointerleave'], clear, listenerOptions),
useEventListener(elementRef, ['pointerup', 'pointerleave'], onRelease, listenerOptions),
]

const stop = () => cleanup.forEach(fn => fn())
Expand Down

0 comments on commit 7346a6a

Please sign in to comment.