diff --git a/README.md b/README.md index 57d4a2e..1b633cc 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ npm i --save @prefecthq/vue-compositions - [useIsSame](https://github.com/prefecthq/vue-compositions/tree/main/src/useIsSame) - [useKeyDown](https://github.com/prefecthq/vue-compositions/tree/main/src/useKeyDown) - [useMedia](https://github.com/prefecthq/vue-compositions/tree/main/src/useMedia) +- [useMousePosition](https://github.com/prefecthq/vue-compositions/tree/main/src/useMousePosition) - [useMutationObserver](https://github.com/prefecthq/vue-compositions/tree/main/src/useMutationObserver) - [useNow](https://github.com/prefecthq/vue-compositions/tree/main/src/useNow) - [usePatchRef](https://github.com/prefecthq/vue-compositions/tree/main/src/usePatchRef) diff --git a/src/index.ts b/src/index.ts index 8d2bc5d..6bd750e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ export * from './useIntersectionObserver' export * from './useIsSame' export * from './useKeyDown' export * from './useMedia' +export * from './useMousePosition' export * from './useMutationObserver' export * from './useNow' export * from './usePatchRef' diff --git a/src/useMousePosition/README.md b/src/useMousePosition/README.md new file mode 100644 index 0000000..8998e0f --- /dev/null +++ b/src/useMousePosition/README.md @@ -0,0 +1,25 @@ +# useMedia +The `useMousePosition` composition is a utility composition that passively tracks the position of the mouse as well as the position of the mouse at last click + +## Example +```typescript +import { useMousePosition } from '@prefecthq/vue-compositions' + +const { position, positionAtLastClick } = useMousePosition() +``` + +## Arguments +None + +## Returns +```ts +type MousePosition { + x: number, + y: number +} + +type UseMousePosition { + position: MousePosition, + positionAtLastClick: MousePosition +} +``` diff --git a/src/useMousePosition/index.ts b/src/useMousePosition/index.ts new file mode 100644 index 0000000..b473f8e --- /dev/null +++ b/src/useMousePosition/index.ts @@ -0,0 +1 @@ +export * from './useMousePosition' \ No newline at end of file diff --git a/src/useMousePosition/useMousePosition.ts b/src/useMousePosition/useMousePosition.ts new file mode 100644 index 0000000..c9effcb --- /dev/null +++ b/src/useMousePosition/useMousePosition.ts @@ -0,0 +1,67 @@ +import { onScopeDispose, reactive, ref } from 'vue' +import { useGlobalEventListener } from '@/useGlobalEventListener' + +export type MousePosition = { + x: number, + y: number, +} + +export type UseMousePosition = { + position: MousePosition, + positionAtLastClick: MousePosition, +} + +const position = reactive({ x: 0, y: 0 }) +const positionAtLastClick = reactive({ x: 0, y: 0 }) + +const updatePositionAtLastClick = (): void => { + Object.assign(positionAtLastClick, position) +} + +const updateMousePosition = (event: MouseEvent): void => { + position.x = event.clientX + position.y = event.clientY + + if (positionAtLastClick.x === 0 && positionAtLastClick.y === 0) { + updatePositionAtLastClick() + } +} + +const listeners = ref(0) + +const { add: addMouseMoveEventListener, remove: removeMouseMoveEventListener } = useGlobalEventListener('mousemove', updateMousePosition, { passive: true }) +const { add: addClickEventListener, remove: removeClickEventListener } = useGlobalEventListener('click', updatePositionAtLastClick, { capture: true }) +const { add: addContextMenuEventListener, remove: removeContextMenuEventListener } = useGlobalEventListener('contextmenu', updatePositionAtLastClick, { capture: true }) + +function tryTeardownEventListeners(): void { + if (listeners.value > 0) { + return + } + + removeMouseMoveEventListener() + removeClickEventListener() + removeContextMenuEventListener() +} + +function addEventListeners(): void { + // These have no effect if the event listeners are already added + addMouseMoveEventListener() + addClickEventListener() + addContextMenuEventListener() +} + +export function useMousePosition(): UseMousePosition { + listeners.value += 1 + + addEventListeners() + + onScopeDispose(() => { + listeners.value -= 1 + tryTeardownEventListeners() + }) + + return { + position, + positionAtLastClick, + } +} \ No newline at end of file