CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vueuse--components

Renderless Vue.js components that expose VueUse composable functionality through declarative template-based interfaces

Pending
Overview
Eval results
Files

device-sensors.mddocs/

Device and Sensors

Components for accessing device information, sensors, and hardware capabilities.

Capabilities

UseBattery Component

Provides battery status information from the Battery Status API.

/**
 * Component that provides battery status information
 * @example
 * <UseBattery v-slot="{ charging, level, dischargingTime }">
 *   <div>Battery: {{ Math.round(level * 100) }}% {{ charging ? 'charging' : 'discharging' }}</div>
 * </UseBattery>
 */
interface UseBatteryProps {
  // No props - uses browser Battery API directly
}

/** Slot data exposed by UseBattery component */
interface BatteryState {
  /** Whether the battery is charging */
  charging: Ref<boolean>;
  /** Time remaining until charged (seconds) */
  chargingTime: Ref<number>;
  /** Time remaining until discharged (seconds) */
  dischargingTime: Ref<number>;
  /** Battery charge level (0-1) */
  level: Ref<number>;
  /** Whether the Battery API is supported */
  isSupported: Ref<boolean>;
}

Usage Examples:

<template>
  <!-- Basic battery info -->
  <UseBattery v-slot="{ charging, level, isSupported }">
    <div v-if="isSupported" class="battery-info">
      <div class="battery-level" :style="{ width: level * 100 + '%' }"></div>
      <span>{{ Math.round(level * 100) }}% {{ charging ? '🔌' : '🔋' }}</span>
    </div>
    <div v-else>Battery API not supported</div>
  </UseBattery>

  <!-- Detailed battery info -->
  <UseBattery v-slot="{ charging, level, chargingTime, dischargingTime }">
    <div class="detailed-battery">
      <h3>Battery Status</h3>
      <p>Level: {{ Math.round(level * 100) }}%</p>
      <p>Status: {{ charging ? 'Charging' : 'Discharging' }}</p>
      <p v-if="charging && chargingTime !== Infinity">
        Time to full: {{ Math.round(chargingTime / 60) }} minutes
      </p>
      <p v-if="!charging && dischargingTime !== Infinity">
        Time remaining: {{ Math.round(dischargingTime / 60) }} minutes
      </p>
    </div>
  </UseBattery>
</template>

<script setup>
import { UseBattery } from '@vueuse/components';
</script>

<style>
.battery-info {
  position: relative;
  width: 200px;
  height: 30px;
  border: 2px solid #333;
  border-radius: 4px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.battery-level {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  background: linear-gradient(90deg, #4caf50, #8bc34a);
  transition: width 0.3s ease;
}
</style>

UseDeviceMotion Component

Provides device motion sensor data including acceleration and rotation.

/**
 * Component that provides device motion sensor data
 * @example
 * <UseDeviceMotion v-slot="{ acceleration, rotationRate }">
 *   <div>Acceleration: {{ acceleration.x.toFixed(2) }}, {{ acceleration.y.toFixed(2) }}</div>
 * </UseDeviceMotion>
 */
interface UseDeviceMotionProps {
  // No props - uses browser DeviceMotion API directly
}

/** Slot data exposed by UseDeviceMotion component */
interface DeviceMotionState {
  /** Device acceleration data */
  acceleration: Ref<DeviceMotionAcceleration | null>;
  /** Device acceleration including gravity */
  accelerationIncludingGravity: Ref<DeviceMotionAcceleration | null>;
  /** Device rotation rate data */
  rotationRate: Ref<DeviceMotionRotationRate | null>;
  /** Motion interval in milliseconds */
  interval: Ref<number>;
  /** Whether the DeviceMotion API is supported */
  isSupported: Ref<boolean>;
}

interface DeviceMotionAcceleration {
  /** Acceleration along x-axis (m/s²) */
  x: number | null;
  /** Acceleration along y-axis (m/s²) */
  y: number | null;
  /** Acceleration along z-axis (m/s²) */
  z: number | null;
}

interface DeviceMotionRotationRate {
  /** Rotation around x-axis (degrees/second) */
  alpha: number | null;
  /** Rotation around y-axis (degrees/second) */
  beta: number | null;
  /** Rotation around z-axis (degrees/second) */
  gamma: number | null;
}

Usage Examples:

<template>
  <!-- Basic motion tracking -->
  <UseDeviceMotion v-slot="{ acceleration, rotationRate, isSupported }">
    <div v-if="isSupported" class="motion-display">
      <h3>Device Motion</h3>
      <div v-if="acceleration">
        <h4>Acceleration (m/s²):</h4>
        <p>X: {{ acceleration.x?.toFixed(2) ?? 'N/A' }}</p>
        <p>Y: {{ acceleration.y?.toFixed(2) ?? 'N/A' }}</p>
        <p>Z: {{ acceleration.z?.toFixed(2) ?? 'N/A' }}</p>
      </div>
      <div v-if="rotationRate">
        <h4>Rotation Rate (°/s):</h4>
        <p>Alpha: {{ rotationRate.alpha?.toFixed(2) ?? 'N/A' }}</p>
        <p>Beta: {{ rotationRate.beta?.toFixed(2) ?? 'N/A' }}</p>
        <p>Gamma: {{ rotationRate.gamma?.toFixed(2) ?? 'N/A' }}</p>
      </div>
    </div>
    <div v-else>Device motion not supported</div>
  </UseDeviceMotion>

  <!-- Motion with gravity -->
  <UseDeviceMotion v-slot="{ accelerationIncludingGravity, interval }">
    <div class="gravity-motion">
      <h3>Motion with Gravity</h3>
      <p>Update interval: {{ interval }}ms</p>
      <div v-if="accelerationIncludingGravity">
        <p>X: {{ accelerationIncludingGravity.x?.toFixed(3) }}</p>
        <p>Y: {{ accelerationIncludingGravity.y?.toFixed(3) }}</p>
        <p>Z: {{ accelerationIncludingGravity.z?.toFixed(3) }}</p>
      </div>
    </div>
  </UseDeviceMotion>
</template>

<script setup>
import { UseDeviceMotion } from '@vueuse/components';
</script>

UseDeviceOrientation Component

Provides device orientation data including compass heading and device tilt.

/**
 * Component that provides device orientation data
 * @example
 * <UseDeviceOrientation v-slot="{ alpha, beta, gamma, absolute }">
 *   <div>Heading: {{ alpha }}° Tilt: {{ beta }}°, {{ gamma }}°</div>
 * </UseDeviceOrientation>
 */
interface UseDeviceOrientationProps {
  // No props - uses browser DeviceOrientation API directly
}

/** Slot data exposed by UseDeviceOrientation component */
interface DeviceOrientationState {
  /** Compass heading (0-360°) */
  alpha: Ref<number | null>;
  /** Front-to-back tilt (-180 to 180°) */
  beta: Ref<number | null>;
  /** Left-to-right tilt (-90 to 90°) */
  gamma: Ref<number | null>;
  /** Whether orientation is absolute (true north) */
  absolute: Ref<boolean>;
  /** Whether the DeviceOrientation API is supported */
  isSupported: Ref<boolean>;
}

Usage Examples:

<template>
  <!-- Basic orientation -->
  <UseDeviceOrientation v-slot="{ alpha, beta, gamma, absolute, isSupported }">
    <div v-if="isSupported" class="orientation-display">
      <h3>Device Orientation</h3>
      <div class="compass" :style="{ transform: `rotate(${alpha || 0}deg)` }">
        <div class="compass-needle"></div>
      </div>
      <p>Compass: {{ alpha?.toFixed(1) ?? 'N/A' }}° {{ absolute ? '(True North)' : '(Magnetic)' }}</p>
      <p>Pitch: {{ beta?.toFixed(1) ?? 'N/A' }}°</p>
      <p>Roll: {{ gamma?.toFixed(1) ?? 'N/A' }}°</p>
    </div>
    <div v-else>Device orientation not supported</div>
  </UseDeviceOrientation>

  <!-- Tilt indicator -->
  <UseDeviceOrientation v-slot="{ beta, gamma }">
    <div class="tilt-indicator">
      <div 
        class="tilt-ball"
        :style="{ 
          transform: `translateX(${(gamma || 0) * 2}px) translateY(${(beta || 0) * 2}px)` 
        }"
      ></div>
      <p>Tilt: {{ beta?.toFixed(1) ?? 'N/A' }}°, {{ gamma?.toFixed(1) ?? 'N/A' }}°</p>
    </div>
  </UseDeviceOrientation>
</template>

<script setup>
import { UseDeviceOrientation } from '@vueuse/components';
</script>

<style>
.compass {
  width: 100px;
  height: 100px;
  border: 2px solid #333;
  border-radius: 50%;
  position: relative;
  margin: 20px auto;
}

.compass-needle {
  position: absolute;
  top: 10%;
  left: 50%;
  width: 2px;
  height: 40%;
  background: red;
  transform: translateX(-50%);
  transform-origin: bottom center;
}

.tilt-indicator {
  width: 200px;
  height: 200px;
  border: 2px solid #333;
  border-radius: 10px;
  position: relative;
  margin: 20px auto;
  overflow: hidden;
}

.tilt-ball {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 20px;
  height: 20px;
  background: #2196f3;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  transition: transform 0.1s ease;
}
</style>

UseDevicePixelRatio Component

Tracks device pixel ratio changes.

/**
 * Component that tracks device pixel ratio
 * @example
 * <UseDevicePixelRatio v-slot="{ pixelRatio }">
 *   <div>Pixel Ratio: {{ pixelRatio }}</div>
 * </UseDevicePixelRatio>
 */
interface UseDevicePixelRatioProps {
  // No props - uses browser devicePixelRatio API
}

/** Slot data exposed by UseDevicePixelRatio component */
interface UseDevicePixelRatioReturn {
  /** Current device pixel ratio */
  pixelRatio: Ref<number>;
}

Usage Examples:

<template>
  <!-- Basic pixel ratio -->
  <UseDevicePixelRatio v-slot="{ pixelRatio }">
    <div class="pixel-ratio-info">
      <h3>Display Information</h3>
      <p>Device Pixel Ratio: {{ pixelRatio }}</p>
      <p>Display Type: {{ getDisplayType(pixelRatio) }}</p>
    </div>
  </UseDevicePixelRatio>

  <!-- Responsive images based on pixel ratio -->
  <UseDevicePixelRatio v-slot="{ pixelRatio }">
    <img 
      :src="getImageSrc(pixelRatio)"
      :alt="`Image for ${pixelRatio}x display`"
      class="responsive-image"
    />
  </UseDevicePixelRatio>
</template>

<script setup>
import { UseDevicePixelRatio } from '@vueuse/components';

function getDisplayType(ratio) {
  if (ratio >= 3) return 'Ultra High DPI';
  if (ratio >= 2) return 'High DPI (Retina)';
  if (ratio >= 1.5) return 'Medium DPI';
  return 'Standard DPI';
}

function getImageSrc(ratio) {
  if (ratio >= 2) return '/images/logo@2x.png';
  return '/images/logo.png';
}
</script>

UseDevicesList Component

Lists available media devices (cameras, microphones, speakers).

/**
 * Component that lists available media devices
 * @example
 * <UseDevicesList v-slot="{ videoInputs, audioInputs, audioOutputs }">
 *   <div>Cameras: {{ videoInputs.length }}, Mics: {{ audioInputs.length }}</div>
 * </UseDevicesList>
 */
interface UseDevicesListProps {
  /** Request permissions for device access @default false */
  requestPermissions?: boolean;
  /** Device constraints for permission request */
  constraints?: MediaStreamConstraints;
  /** Device change callback */
  onUpdated?: (devices: MediaDeviceInfo[]) => void;
}

/** Slot data exposed by UseDevicesList component */
interface UseDevicesListReturn {
  /** All available devices */
  devices: Ref<MediaDeviceInfo[]>;
  /** Video input devices (cameras) */
  videoInputs: Ref<MediaDeviceInfo[]>;
  /** Audio input devices (microphones) */
  audioInputs: Ref<MediaDeviceInfo[]>;
  /** Audio output devices (speakers) */
  audioOutputs: Ref<MediaDeviceInfo[]>;
  /** Whether the MediaDevices API is supported */
  isSupported: Ref<boolean>;
  /** Permissions granted status */
  permissionGranted: Ref<boolean>;
  /** Refresh device list */
  ensurePermissions: () => Promise<boolean>;
  /** Update device list */
  update: () => Promise<void>;
}

interface MediaDeviceInfo {
  deviceId: string;
  kind: 'videoinput' | 'audioinput' | 'audiooutput';
  label: string;
  groupId: string;
}

interface MediaStreamConstraints {
  video?: boolean | MediaTrackConstraints;
  audio?: boolean | MediaTrackConstraints;
}

Usage Examples:

<template>
  <!-- Basic device list -->
  <UseDevicesList v-slot="{ videoInputs, audioInputs, audioOutputs, isSupported }">
    <div v-if="isSupported" class="devices-list">
      <h3>Available Devices</h3>
      
      <div class="device-category">
        <h4>Cameras ({{ videoInputs.length }})</h4>
        <ul>
          <li v-for="device in videoInputs" :key="device.deviceId">
            {{ device.label || `Camera ${device.deviceId.slice(0, 8)}...` }}
          </li>
        </ul>
      </div>

      <div class="device-category">
        <h4>Microphones ({{ audioInputs.length }})</h4>
        <ul>
          <li v-for="device in audioInputs" :key="device.deviceId">
            {{ device.label || `Microphone ${device.deviceId.slice(0, 8)}...` }}
          </li>
        </ul>
      </div>

      <div class="device-category">
        <h4>Speakers ({{ audioOutputs.length }})</h4>
        <ul>
          <li v-for="device in audioOutputs" :key="device.deviceId">
            {{ device.label || `Speaker ${device.deviceId.slice(0, 8)}...` }}
          </li>
        </ul>
      </div>
    </div>
    <div v-else>Media devices not supported</div>
  </UseDevicesList>

  <!-- With permissions -->
  <UseDevicesList 
    :request-permissions="true" 
    :constraints="{ video: true, audio: true }"
    v-slot="{ devices, permissionGranted, ensurePermissions, update }"
  >
    <div class="permissions-devices">
      <div v-if="!permissionGranted" class="permission-request">
        <p>Camera and microphone access required</p>
        <button @click="ensurePermissions">Grant Permissions</button>
      </div>
      <div v-else class="devices-granted">
        <p>Found {{ devices.length }} devices</p>
        <button @click="update">Refresh List</button>
        <div v-for="device in devices" :key="device.deviceId" class="device-item">
          <span class="device-type">{{ device.kind }}</span>
          <span class="device-label">{{ device.label }}</span>
        </div>
      </div>
    </div>
  </UseDevicesList>
</template>

<script setup>
import { UseDevicesList } from '@vueuse/components';
</script>

<style>
.device-category {
  margin: 15px 0;
}

.device-category h4 {
  margin: 5px 0;
  color: #666;
}

.device-category ul {
  list-style: none;
  padding: 0;
}

.device-category li {
  padding: 5px 0;
  border-bottom: 1px solid #eee;
}

.device-item {
  display: flex;
  justify-content: space-between;
  padding: 8px;
  border: 1px solid #ddd;
  margin: 5px 0;
  border-radius: 4px;
}

.device-type {
  font-weight: bold;
  text-transform: capitalize;
}

.permission-request {
  text-align: center;
  padding: 20px;
  border: 2px dashed #ccc;
  border-radius: 8px;
}
</style>

Type Definitions

/** Common types used across device and sensor components */
type MaybeRefOrGetter<T> = T | Ref<T> | (() => T);

interface RenderableComponent {
  /** The element that the component should be rendered as @default 'div' */
  as?: object | string;
}

/** Battery API types */
interface BatteryManager {
  charging: boolean;
  chargingTime: number;
  dischargingTime: number;
  level: number;
  onchargingchange: ((this: BatteryManager, ev: Event) => any) | null;
  onchargingtimechange: ((this: BatteryManager, ev: Event) => any) | null;
  ondischargingtimechange: ((this: BatteryManager, ev: Event) => any) | null;
  onlevelchange: ((this: BatteryManager, ev: Event) => any) | null;
}

/** Device motion and orientation types */
interface DeviceMotionEvent extends Event {
  acceleration: DeviceMotionAcceleration | null;
  accelerationIncludingGravity: DeviceMotionAcceleration | null;
  rotationRate: DeviceMotionRotationRate | null;
  interval: number;
}

interface DeviceOrientationEvent extends Event {
  alpha: number | null;
  beta: number | null;
  gamma: number | null;
  absolute: boolean;
}

Install with Tessl CLI

npx tessl i tessl/npm-vueuse--components

docs

browser-apis.md

browser-events.md

device-sensors.md

element-tracking.md

index.md

mouse-pointer.md

scroll-resize.md

theme-preferences.md

utilities-advanced.md

window-document.md

tile.json