Is it possible to pass an HTMLElement into a composable? If so how is the correct way to do this.
After following Vue's examples on composables I wanted to create a similar example to their mouse position example except restricting it to the element that was passed in the composable function.
The problem I am having is that passing a reference to the element before the onMounted lifecycle method means null is passed. Also the onMounted method in the composable doesn't run if I call the onMounted in the parent component.
I'm not really sure how to start an example since I've tried so many things. Are there an examples of this anywhere? I have checked the docs and I don't see anything with composables and elements. Maybe this isn't how they are suppose to be used?
My ideal final result would be the following: ( This is NOT working code )
mouse.ts
import { ref, watchEffect, onMounted, onUnmounted } from "vue"
import type { Ref } from "vue"
interface Coordinate {
x: Ref
y: Ref
}
export function useMouseIn(element: HTMLElement | null): Coordinate {
const x = ref(0)
const y = ref(0)
if (!element) {
return { x , y }
}
function update(event: MouseEvent) {
if (!element) {
return { x, y }
}
const rect = element.getBoundingClientRect()
x.value = Math.ceil(event.clientX - rect.left)
y.value = Math.ceil(event.clientY - rect.top)
x.value = x.value < 0 ? x.value = 0 : x.value
y.value = y.value < 0 ? y.value = 0 : y.value
}
onMounted(() => element.removeEventListener("mousemove", update))
onUnmounted(() => element.removeEventListener("mousemove", update))
return { x, y }
}
Panel.vue
<script setup lang="ts">
import { ref } from "vue"
import { useMouseIn } from "../composables/mouse"
const panel = ref(null)
const { x, y } = useMouseIn(panel.value)
</script>
<template>
<div
ref="panel"
>
{{ x }}, {{ y }}
</div>
</template>
<style scoped>
.panel {
width: 200px;
height: 200px;
background-color: grey;
}
</style>
App.vue
<script setup lang="ts">
import Panel from "./components/Panel.vue"
</script>
<template>
<div>
<Panel />
</div>
</template>
Any help is appreciated. I can't seem to find examples like this on the Vue docs but maybe there is a library I can look at to get some ideas.
CodePudding user response:
Yes, you can pass in elements, and if you pass them in as refs, you can make it dynamic. Setup changes a bit, because we have to remove and add event listeners every time the element changes:
import { ref, isRef, unref, watch, onUnmounted } from "vue"
export function useMouseIn(element) {
const x = ref(0)
const y = ref(0)
function setMouseListeners(){
unref(element)?.addEventListener("mousemove", update)
}
function updateMouseListeners(newElement, oldElement){
unref(oldElement)?.removeEventListener("mousemove", update)
setMouseListeners()
}
function update(event) {
const rect = this.getBoundingClientRect();
x.value = event.clientX - rect.left;
y.value = event.clientY - rect.top;
}
if (isRef(element)) {
watch(element, updateMouseListeners, {immediate: true})
} else {
setMouseListeners()
}
onUnmounted(() => unref(element)?.removeEventListener("mousemove", update))
return { x, y }
}
Here is a sandbox based on your example.