Home > front end >  Using Vue 3's compisotion api is it possible to make composables for elements passed in a metho
Using Vue 3's compisotion api is it possible to make composables for elements passed in a metho

Time:02-04

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.

  • Related