Composable in a class
Pointer hosts useMouse behind a private $-getter: the composable is created once, on the first read, and cached for the life of the instance. The public surface is two refs — x and y.
What to notice
- The composable is an implementation detail. Swap
useMousefor any other source of coordinates and no consumer changes. - Scope-correct teardown. The component's state destructure materializes
$mouseinside setup, so its listeners are cleaned up on unmount.
The source
ts
// Pointer.ts — a class HOSTING a composable: private inside, two refs outside.
import { Reactive } from '../../ivue';
import { useMouse } from '@vueuse/core';
class $Pointer {
// the composable is an implementation detail — created once, held forever
private get $mouse() {
return useMouse();
}
// the public surface: two refs
get x() {
return this.$mouse.x;
}
get y() {
return this.$mouse.y;
}
// display derivations — touch events report fractional page coordinates
// (23.333…); whole pixels are what a readout wants
get pageX() {
return Math.round(this.x.value);
}
get pageY() {
return Math.round(this.y.value);
}
}
export namespace Pointer {
export const $Class = $Pointer; // raw — children `extends` this
export let Class = Reactive($Class); // reactive — you `new` this
export type Instance = typeof Class.Instance; // defineExpose type & reactive() interop
}vue
<script setup lang="ts">
import { Pointer } from './Pointer';
// materializes $mouse inside the component's scope, so its listeners are
// cleaned up on unmount
const pointer = new Pointer.Class();
</script>
<template>
<div class="pane">
<p class="note">
useMouse lives inside the class — private, created once on the first
read. The readouts are pageX/pageY, plain getters that round the raw
coordinates to whole pixels.
</p>
<div class="vals">
<div>
<div class="k">x · page</div>
<div class="n">{{ pointer.pageX }}</div>
</div>
<div>
<div class="k">y · page</div>
<div class="n">{{ pointer.pageY }}</div>
</div>
</div>
</div>
</template>
<style scoped src="../example-pane.css"></style>Open in StackBlitz ⚡ — the playground boots with this example's route and file active.