From 4bb5bf0e1f73472e10a0192392b823745defc949 Mon Sep 17 00:00:00 2001 From: RAX7 Date: Sat, 22 Apr 2023 16:02:06 +0700 Subject: [PATCH] feat(useMouse): support custom event extractor (#2991) Co-authored-by: Anthony Fu --- packages/core/useMouse/demo.vue | 23 +++++- packages/core/useMouse/index.md | 25 ++++++ packages/core/useMouse/index.ts | 103 +++++++++++++------------ packages/core/useMousePressed/index.ts | 6 +- 4 files changed, 102 insertions(+), 55 deletions(-) diff --git a/packages/core/useMouse/demo.vue b/packages/core/useMouse/demo.vue index 69d27fc99f5..5b7964724ec 100644 --- a/packages/core/useMouse/demo.vue +++ b/packages/core/useMouse/demo.vue @@ -1,12 +1,27 @@ diff --git a/packages/core/useMouse/index.md b/packages/core/useMouse/index.md index 853a472352a..2a6ffac8dc5 100644 --- a/packages/core/useMouse/index.md +++ b/packages/core/useMouse/index.md @@ -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 diff --git a/packages/core/useMouse/index.ts b/packages/core/useMouse/index.ts index 88cd0201321..4cc6bb069b0 100644 --- a/packages/core/useMouse/index.ts +++ b/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 /** * Listen to `touchmove` events @@ -33,7 +44,16 @@ export interface UseMouseOptions extends ConfigurableWindow, ConfigurableEventFi initialValue?: Position } -export type MouseSourceType = 'mouse' | 'touch' | null +const BuiltinExtractors: Record = { + 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. @@ -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(null) + const sourceType = ref(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 }) } } diff --git a/packages/core/useMousePressed/index.ts b/packages/core/useMousePressed/index.ts index f257fc0b26f..ddacf0a8436 100644 --- a/packages/core/useMousePressed/index.ts +++ b/packages/core/useMousePressed/index.ts @@ -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' @@ -49,7 +49,7 @@ export function useMousePressed(options: MousePressedOptions = {}) { } = options const pressed = ref(initialValue) - const sourceType = ref(null) + const sourceType = ref(null) if (!window) { return { @@ -58,7 +58,7 @@ export function useMousePressed(options: MousePressedOptions = {}) { } } - const onPressed = (srcType: MouseSourceType) => () => { + const onPressed = (srcType: UseMouseSourceType) => () => { pressed.value = true sourceType.value = srcType }