Skip to content

Commit

Permalink
feat(useMouse): support custom event extractor (#2991)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
RAX7 and antfu committed Apr 22, 2023
1 parent 4a7a12c commit 4bb5bf0
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 55 deletions.
23 changes: 19 additions & 4 deletions packages/core/useMouse/demo.vue
@@ -1,12 +1,27 @@
<script setup lang="ts">
import { reactive } from 'vue'
import { stringify } from '@vueuse/docs-utils'
import { useMouse } from '@vueuse/core'
import { useMouse, useParentElement } from '@vueuse/core'
import type { UseMouseEventExtractor } from '@vueuse/core'
const mouse = reactive(useMouse())
const text = stringify(mouse)
const parentEl = useParentElement()
const mouseDefault = reactive(useMouse())
const textDefault = stringify(mouseDefault)
const extractor: UseMouseEventExtractor = event => (
event instanceof Touch
? null
: [event.offsetX, event.offsetY]
)
const mouseWithExtractor = reactive(useMouse({ target: parentEl, type: extractor }))
const textWithExtractor = stringify(mouseWithExtractor)
</script>

<template>
<pre lang="yaml">{{ text }}</pre>
<p>Basic Usage</p>
<pre lang="yaml">{{ textDefault }}</pre>
<p>Extractor Usage</p>
<pre lang="yaml">{{ textWithExtractor }}</pre>
</template>
25 changes: 25 additions & 0 deletions packages/core/useMouse/index.md
Expand Up @@ -21,6 +21,31 @@ The `dragover` event is used to track mouse position while dragging.
const { x, y } = useMouse({ touch: false })
```

## Custom Extractor

It's also possible to provide a custom extractor function to get the position from the event.

```js
import { type UseMouseEventExtractor, useMouse, useParentElement } from '@vueuse/core'

const parentEl = useParentElement()

const extractor: UseMouseEventExtractor = event => (
event instanceof Touch
? null
: [event.offsetX, event.offsetY]
)

const { x, y, sourceType } = useMouse({ target: parentEl, type: extractor })
```

Touch is enabled by default. To only detect mouse changes, set `touch` to `false`.
The `dragover` event is used to track mouse position while dragging.

```js
const { x, y } = useMouse({ touch: false })
```

## Component Usage

```html
Expand Down
103 changes: 55 additions & 48 deletions packages/core/useMouse/index.ts
@@ -1,17 +1,28 @@
import { ref } from 'vue-demi'
import type { ConfigurableEventFilter } from '@vueuse/shared'
import type { ConfigurableEventFilter, MaybeRefOrGetter } from '@vueuse/shared'
import { useEventListener } from '../useEventListener'
import type { ConfigurableWindow } from '../_configurable'
import { defaultWindow } from '../_configurable'
import type { Position } from '../types'

export type UseMouseCoordType = 'page' | 'client' | 'screen' | 'movement'
export type UseMouseSourceType = 'mouse' | 'touch' | null
export type UseMouseEventExtractor = (event: MouseEvent | Touch) => [x: number, y: number] | null | undefined

export interface UseMouseOptions extends ConfigurableWindow, ConfigurableEventFilter {
/**
* Mouse position based by page, client, screen, or relative to previous position
*
* @default 'page'
*/
type?: 'page' | 'client' | 'screen' | 'movement'
type?: UseMouseCoordType | UseMouseEventExtractor

/**
* Listen events on `target` element
*
* @default 'Window'
*/
target?: MaybeRefOrGetter<Window | EventTarget | null | undefined>

/**
* Listen to `touchmove` events
Expand All @@ -33,7 +44,16 @@ export interface UseMouseOptions extends ConfigurableWindow, ConfigurableEventFi
initialValue?: Position
}

export type MouseSourceType = 'mouse' | 'touch' | null
const BuiltinExtractors: Record<UseMouseCoordType, UseMouseEventExtractor> = {
page: event => [event.pageX, event.pageY],
client: event => [event.clientX, event.clientY],
screen: event => [event.screenX, event.screenY],
movement: event => (
event instanceof Touch
? null
: [event.movementX, event.movementY]
),
} as const

/**
* Reactive mouse position.
Expand All @@ -48,71 +68,58 @@ export function useMouse(options: UseMouseOptions = {}) {
resetOnTouchEnds = false,
initialValue = { x: 0, y: 0 },
window = defaultWindow,
target = window,
eventFilter,
} = options

const x = ref(initialValue.x)
const y = ref(initialValue.y)
const sourceType = ref<MouseSourceType>(null)
const sourceType = ref<UseMouseSourceType>(null)

const extractor = typeof type === 'function'
? type
: BuiltinExtractors[type]

const mouseHandler = (event: MouseEvent) => {
if (type === 'page') {
x.value = event.pageX
y.value = event.pageY
}
else if (type === 'client') {
x.value = event.clientX
y.value = event.clientY
}
else if (type === 'screen') {
x.value = event.screenX
y.value = event.screenY
}
else if (type === 'movement') {
x.value = event.movementX
y.value = event.movementY
const result = extractor(event)

if (result) {
[x.value, y.value] = result
sourceType.value = 'mouse'
}
sourceType.value = 'mouse'
}
const reset = () => {
x.value = initialValue.x
y.value = initialValue.y
}

const touchHandler = (event: TouchEvent) => {
if (event.touches.length > 0) {
const touch = event.touches[0]
if (type === 'page') {
x.value = touch.pageX
y.value = touch.pageY
}
else if (type === 'client') {
x.value = touch.clientX
y.value = touch.clientY
}
else if (type === 'screen') {
x.value = touch.screenX
y.value = touch.screenY
const result = extractor(event.touches[0])
if (result) {
[x.value, y.value] = result
sourceType.value = 'touch'
}
sourceType.value = 'touch'
}
}

const mouseHandlerWrapper = (event: MouseEvent) => {
return eventFilter === undefined ? mouseHandler(event) : eventFilter(() => mouseHandler(event), {} as any)
const reset = () => {
x.value = initialValue.x
y.value = initialValue.y
}

const touchHandlerWrapper = (event: TouchEvent) => {
return eventFilter === undefined ? touchHandler(event) : eventFilter(() => touchHandler(event), {} as any)
}
const mouseHandlerWrapper = eventFilter
? (event: MouseEvent) => eventFilter(() => mouseHandler(event), {} as any)
: (event: MouseEvent) => mouseHandler(event)

const touchHandlerWrapper = eventFilter
? (event: TouchEvent) => eventFilter(() => touchHandler(event), {} as any)
: (event: TouchEvent) => touchHandler(event)

if (window) {
useEventListener(window, 'mousemove', mouseHandlerWrapper, { passive: true })
useEventListener(window, 'dragover', mouseHandlerWrapper, { passive: true })
if (target) {
useEventListener(target, 'mousemove', mouseHandlerWrapper, { passive: true })
useEventListener(target, 'dragover', mouseHandlerWrapper, { passive: true })
if (touch && type !== 'movement') {
useEventListener(window, 'touchstart', touchHandlerWrapper, { passive: true })
useEventListener(window, 'touchmove', touchHandlerWrapper, { passive: true })
useEventListener(target, 'touchstart', touchHandlerWrapper, { passive: true })
useEventListener(target, 'touchmove', touchHandlerWrapper, { passive: true })
if (resetOnTouchEnds)
useEventListener(window, 'touchend', reset, { passive: true })
useEventListener(target, 'touchend', reset, { passive: true })
}
}

Expand Down
6 changes: 3 additions & 3 deletions packages/core/useMousePressed/index.ts
Expand Up @@ -2,7 +2,7 @@ import { computed, ref } from 'vue-demi'
import type { MaybeElementRef } from '../unrefElement'
import { unrefElement } from '../unrefElement'
import { useEventListener } from '../useEventListener'
import type { MouseSourceType } from '../useMouse'
import type { UseMouseSourceType } from '../useMouse'
import type { ConfigurableWindow } from '../_configurable'
import { defaultWindow } from '../_configurable'

Expand Down Expand Up @@ -49,7 +49,7 @@ export function useMousePressed(options: MousePressedOptions = {}) {
} = options

const pressed = ref(initialValue)
const sourceType = ref<MouseSourceType>(null)
const sourceType = ref<UseMouseSourceType>(null)

if (!window) {
return {
Expand All @@ -58,7 +58,7 @@ export function useMousePressed(options: MousePressedOptions = {}) {
}
}

const onPressed = (srcType: MouseSourceType) => () => {
const onPressed = (srcType: UseMouseSourceType) => () => {
pressed.value = true
sourceType.value = srcType
}
Expand Down

0 comments on commit 4bb5bf0

Please sign in to comment.