Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/vueuse/main'
Browse files Browse the repository at this point in the history
# Conflicts:
#	packages/core/onLongPress/demo.vue
#	scripts/changelog.ts
  • Loading branch information
Alfred-Skyblue committed May 27, 2024
2 parents 16d91e3 + 34c047b commit a396459
Show file tree
Hide file tree
Showing 18 changed files with 170 additions and 17 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@vueuse/monorepo",
"type": "module",
"version": "10.9.0",
"version": "10.10.0",
"private": true,
"packageManager": "[email protected]",
"description": "Collection of essential Vue Composition Utilities",
Expand Down
2 changes: 1 addition & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vueuse/components",
"version": "10.9.0",
"version": "10.10.0",
"description": "Renderless components for VueUse",
"author": "Jacob Clevenger<https://github.com/wheatjs>",
"license": "MIT",
Expand Down
27 changes: 25 additions & 2 deletions 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>长按状态: <BooleanDisplay :value="longPressed" /></p>
<p>长按: <BooleanDisplay :value="longPressed" /></p>
<p>点击: <BooleanDisplay :value="clicked" /></p>
<button ref="htmlRef" class="ml-2 button small">
长按(500毫秒)
</button>
<button ref="htmlRefOptions" class="ml-2 button small">
长按(1000毫秒)
</button>
<button ref="htmlRefOnMouseUp" class="ml-2 button small">
长按(1000毫秒)或点击
</button>
<button class="ml-2 button small" @click="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 @@ -22,6 +22,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 @@ -41,13 +49,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 @@ -66,8 +100,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 @@ -100,7 +138,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
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vueuse/core",
"version": "10.9.0",
"version": "10.10.0",
"description": "Collection of essential Vue Composition Utilities",
"author": "Anthony Fu <https://github.com/antfu>",
"license": "MIT",
Expand Down
29 changes: 29 additions & 0 deletions packages/core/useActiveElement/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { nextTick } from 'vue'
import { useActiveElement } from '.'

describe('useActiveElement', () => {
Expand Down Expand Up @@ -59,4 +60,32 @@ describe('useActiveElement', () => {

expect(activeElement.value).to.equal(document.body)
})

it('should update when activeElement is removed w/document', async () => {
const activeElement = useActiveElement({ triggerOnRemoval: true })

input.focus()

expect(activeElement.value).to.equal(input)

input.remove()

await nextTick()

expect(activeElement.value).to.equal(document.body)
})

it('should update when activeElement is removed w/shadowRoot', async () => {
const activeElement = useActiveElement({ triggerOnRemoval: true, document: shadowRoot })

shadowInput.focus()

expect(activeElement.value).to.equal(shadowInput)

shadowInput.remove()

await nextTick()

expect(activeElement.value).to.equal(null)
})
})
21 changes: 21 additions & 0 deletions packages/core/useActiveElement/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ref } from 'vue-demi'
import { useEventListener } from '../useEventListener'
import type { ConfigurableDocumentOrShadowRoot, ConfigurableWindow } from '../_configurable'
import { defaultWindow } from '../_configurable'
import { useMutationObserver } from '../useMutationObserver'

export interface UseActiveElementOptions extends ConfigurableWindow, ConfigurableDocumentOrShadowRoot {
/**
Expand All @@ -10,6 +11,12 @@ export interface UseActiveElementOptions extends ConfigurableWindow, Configurabl
* @default true
*/
deep?: boolean
/**
* Track active element when it's removed from the DOM
* Using a MutationObserver under the hood
* @default false
*/
triggerOnRemoval?: boolean
}

/**
Expand All @@ -24,6 +31,7 @@ export function useActiveElement<T extends HTMLElement>(
const {
window = defaultWindow,
deep = true,
triggerOnRemoval = false,
} = options
const document = options.document ?? window?.document

Expand All @@ -50,6 +58,19 @@ export function useActiveElement<T extends HTMLElement>(
useEventListener(window, 'focus', trigger, true)
}

if (triggerOnRemoval) {
useMutationObserver(document as any, (mutations) => {
mutations.filter(m => m.removedNodes.length)
.map(n => Array.from(n.removedNodes)).flat().forEach((node) => {
if (node === activeElement.value)
trigger()
})
}, {
childList: true,
subtree: true,
})
}

trigger()

return activeElement
Expand Down
2 changes: 1 addition & 1 deletion packages/electron/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vueuse/electron",
"version": "10.9.0",
"version": "10.10.0",
"description": "Electron renderer process modules for VueUse",
"author": "Archer Gu<https://github.com/ArcherGu>",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/firebase/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vueuse/firebase",
"version": "10.9.0",
"version": "10.10.0",
"description": "Enables realtime bindings for Firebase",
"author": "Anthony Fu <https://github.com/antfu>",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/integrations/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vueuse/integrations",
"version": "10.9.0",
"version": "10.10.0",
"description": "Integration wrappers for utility libraries",
"author": "Anthony Fu <https://github.com/antfu>",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/math/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vueuse/math",
"version": "10.9.0",
"version": "10.10.0",
"description": "Math functions for VueUse",
"author": "Anthony Fu <https://github.com/antfu>",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/metadata/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@vueuse/metadata",
"type": "module",
"version": "10.9.0",
"version": "10.10.0",
"description": "Metadata for VueUse functions",
"author": "Anthony Fu <https://github.com/antfu>",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/nuxt/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@vueuse/nuxt",
"type": "module",
"version": "10.9.0",
"version": "10.10.0",
"description": "VueUse Nuxt Module",
"author": "Anthony Fu <https://github.com/antfu>",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/router/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vueuse/router",
"version": "10.9.0",
"version": "10.10.0",
"description": "Utilities for vue-router",
"author": "Anthony Fu <https://github.com/antfu>",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/rxjs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vueuse/rxjs",
"version": "10.9.0",
"version": "10.10.0",
"description": "Enables RxJS reactive functions in Vue",
"author": "Anthony Fu <https://github.com/antfu>",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vueuse/shared",
"version": "10.9.0",
"version": "10.10.0",
"author": "Anthony Fu <https://github.com/antfu>",
"license": "MIT",
"funding": "https://github.com/sponsors/antfu",
Expand Down
2 changes: 1 addition & 1 deletion scripts/changelog.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import md5 from 'md5'
import Git from 'simple-git'
import type { CommitInfo, ContributorInfo } from '@vueuse/metadata'
import { functions } from '../packages/metadata'
import { functions } from '../packages/metadata/metadata'
import { uniq } from './utils'

const git = Git({
Expand Down

0 comments on commit a396459

Please sign in to comment.